Java虚拟机(一) —— 运行时数据区

JVM构成

运行时数据区

Java 虚拟机的内存空间分为 5 个部分:

  • 程序计数器
  • Java 虚拟机栈
  • 本地方法栈
  • 方法区

程序计数器(PC)

为什么需要程序计数器

因为Java虚拟机的多线程是通过线程轮流切换分配处理器执行时间的方式来实现的。任意时刻,一个处理器只执行一条指令,为了进程切换后恢复到正确的执行位置,所以才有了程序计数器

作用

记录当前线程的执行字节码的位置,线程私有,也就是每个线程都有一个单独的计数器来记录。

Java虚拟机栈

描述Java方法执行的内存模型。

方法在执行的时候会创建一个栈帧,方法的调用到完成,对应着栈帧在虚拟机栈中入栈出栈的过程。

一个线程的方法调用可能很长,很多方法都处于执行状态。对于执行引擎来说,只有处于栈顶的栈帧(当前栈帧)才有效,与之相关联的方法是当前方法

栈帧用于存储局部变量表操作数栈动态链接方法出口等信息。

栈帧

局部变量表

局部变量表存放编译期可知的基本数据类型对象引用return Address类型

局部变量表所需要的内存空间在编译期间完成分配。

特点

  • 线程私有
  • 线程请求的栈深度大于JVM允许深度,抛出StackOverflowError异常
  • 虚拟机栈动态扩展时,无法申请足够的内存,抛出OutOfMemoryError异常

堆的特点

  • 线程共享
  • 垃圾回收的主要场所

用来存放对象的内存空间,几乎所有的对象都存储在堆中。

对象在堆内创建的流程

对象在创建时,会放到Eden区,当Eden区满了,会进行MinorGC,生存的对象会放到Survivor From区中,若Survivor From区中无可用内存,

则再进行MonorGC,对Eden、From区进行清理,存活的对象放入Survivor To区,To就转换为From区,原先的From区就变为To区。

  • MinorGC时,大对象直接进入老年代
    • 大对象若From区放不下,会直接放到老年代,避免大对象在From和To区来回复制,影响性能。
  • 长期存活的对象直接进入老年代
    • 对象每经过一次MinorGC,年龄+1,增加到15岁,进入老年代,-XX:MaxTenuringThreshold设置年龄阈值

对象创建分配程序演示

使用vm参数

-Xms20M
-Xmx20M
-XX:+PrintGCDetails

程序实例1:创建的对象首先分配到Eden区

public class ObjectAllocationDemo {

    public static void main(String[] args) {
        byte[] a = new byte[5 * 1024];
    }

}

运行结果

对象填满Eden区


public class ObjectAllocationDemo {

   public static void main(String[] args) {
       byte[] a = new byte[1024* 2 * 1024];
       byte[] b = new byte[1024*2 * 1024];
       byte[] c = new byte[300* 1024];
   }

}

程序实例2:Eden区满了以后,会进行一次MinorGC,将存活对象放到From区,From区放不下,就放到Old代空间。

public class ObjectAllocationDemo {

    public static void main(String[] args) {
        byte[] a = new byte[1024* 2 * 1024];
        byte[] b = new byte[1024*2 * 1024];
        byte[] c = new byte[300* 1024];
        byte[] d = new byte[90* 1024];
    }

}

运行结果

栈操作流程分析(一)

public class TestStack {

    public int math() {
        int a = 1;
        int b = 2;
        int c = a + b;
        return c;
    }

    public static void main(String[] args) {
        TestStack testStack = new TestStack();
        System.out.println(testStack.math());
    }

}

执行javac TestStack.javajavap -c -l TestStack > teststack.txt

得到teststack.txt

Compiled from "TestStack.java"
public class com.fonxian.base.jvm.TestStack {
  public com.fonxian.base.jvm.TestStack();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 7: 0

  public int math();
    Code:
       // 将int类型常量1压入栈
       // 当我把代码改成int a = 9时,得到的结果是bipush 9  istore_1
       0: iconst_1
       // istore 将int类型值存入局部变量1,这里的意思是放到第1个局部变量中
       1: istore_1
       2: iconst_2
       3: istore_2
       // iload_1 从局部变量1中装载int类型值
       4: iload_1
       5: iload_2
       // iadd 执行int类型的加法
       6: iadd
       7: istore_3
       8: iload_3
       // ireturn 从方法中返回int类型的数据
       9: ireturn
    LineNumberTable:
      line 10: 0
      line 11: 2
      line 12: 4
      line 13: 8

  public static void main(java.lang.String[]);
    Code:
       // new 创建一个新对象
       0: new           #2                  // class com/fonxian/base/jvm/TestStack
       // 复制栈顶部一个字长内容
       3: dup
       // invokespecial 根据编译时类型来调用实例方法
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: invokevirtual #5                  // Method math:()I
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      18: return
    LineNumberTable:
      line 17: 0
      line 18: 8
      line 19: 18
}

流程示意

执行第0行代码iconst_1

执行第1行代码istore_1

执行第5行代码iload_2

执行第6行代码iadd

栈操作流程分析(二)


public class IntegerDemo {


    public void test() {
        int a = 10000;
        Integer b = 1000;
        System.out.println(a);
        System.out.println(b);
    }

    public static void main(String[] args) {
        new IntegerDemo().test();
    }

}

反编译后得到

Compiled from "IntegerDemo.java"
public class com.fonxian.base.jvm.IntegerDemo {
  public com.fonxian.base.jvm.IntegerDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 7: 0

  public void test();
    Code:
       0: sipush        10000
       3: istore_1
       4: sipush        1000
       7: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      10: astore_2
      11: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      14: iload_1
      15: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
      18: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: aload_2
      22: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      25: return
    LineNumberTable:
      line 11: 0
      line 12: 4
      line 13: 11
      line 14: 18
      line 15: 25

  public static void main(java.lang.String[]);
    Code:
       0: new           #6                  // class com/fonxian/base/jvm/IntegerDemo
       3: dup
       4: invokespecial #7                  // Method "<init>":()V
       7: invokevirtual #8                  // Method test:()V
      10: return
    LineNumberTable:
      line 18: 0
      line 19: 10
}

JVM指令

当int取值-15采用iconst指令,取值-128127采用bipush指令,取值-3276832767采用sipush指令,取值-21474836482147483647采用 ldc 指令。

指令 作用
iconst_1 将int类型常量1压入(操作数)栈
istore_1 istore_1 将int类型值存入局部变量1,局部变量1可能是任何int类型数
ireturn 从方法中返回int类型的数据
iadd 执行int类型的加法
bipush 将一个8位带符号整数压入栈

参考文档

《深入理解Java虚拟机》

posted @ 2016-05-15 08:35  清泉白石  阅读(546)  评论(0编辑  收藏  举报