Jvm运行时区域的学习

一:结构总览

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

 

  • heap: 堆,整个虚拟机共用一个堆。类对象的实例放在这里。
  • MethodArea:方法区。一个Class文件加载出来那些字节码信息,就放在这里。方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。Class文件字节码信息放在方法区,加载出来的类实例放在堆区。这涉及到ClassLoader动态加载的问题了。
  • Runtime constant pool: 常量池。放那些在Class文件中定义的那些常量啊!!
  • 本地方法栈、虚拟机栈、程序计数器都是线程私有的,每个线程有一份。本地方法栈和虚拟机栈放的是栈帧。
  • 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
    如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。程序计数器的值由执行引擎更改。

  • 栈帧包括:局部变量表,操作数栈,动态链接,方法出口。

 

二:栈帧

  • 局部变量表:存放了基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型。
  • 操作数栈:当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。譬如在做算术运算的时候,是通过将运算涉及的操作数栈压入栈顶后调用运算指令来进行的。譬如在调用其他方法的时候是通过操作数栈来进行方法参数的传递。举个例子,例如整数加法的字节码指令iadd,这条指令在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会把这两个 int 值出栈并相加,然后将相加的结果重新入栈。
  • 动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
  • 方法出口:当一个方法开始执行后,只有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,方法是否有返回值以及返回值的类型将根据遇到何种方法返回指令来决定。另外一种退出方式是在方法执行的过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者提供任何返回值的。无论采用何种退出方式,在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。

 

比如说这一段code的反编译为:

public void func1() {
        int a = 1;
        int b = 2;
        int c = 3;
    }

 public void func1();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iconst_3
         5: istore_3
         6: return

stack1就是操作数栈大小,locals就是局部变量表大小。操作数栈和本地变量表的⼤⼩的单位为 Slot , double 类型和 long 类型会 占⽤ 2 个 Slot。(因为参数默认会添加一个this,所以参数个数会是1)

 

 

三:垃圾回收

在java中,可作为GC Roots的对象有:

1.方法区中常量引用的对象;

2.方法区中的类静态属性引用的对象;

3.虚拟机栈的栈帧的局部变量表中引用的对象;

4.本地方法栈的栈帧中引用的对象.(参考【证】:那些可作为GC Roots的对象_Etyero的博客-CSDN博客_gcroots的对象)

 

讲解:1 和 2都挺好理解的,因为有static标识,所以字段属于类,而不属于某个变量。

注意,GC Roots是变量,1 2 中变量所指向的对象,被GC root引用,所以不会被GC掉,不要误以为在堆中的对象本身是GC root。

 

注意,当栈帧从栈中出栈,原来可以作为GCroot的变量随方法消失,旧GCroot没有被其他GCroot引用,被回收!!

 

修正:所有GC root对象 https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3

 

posted @ 2021-12-30 23:25  ou尼酱~~~  阅读(92)  评论(0)    收藏  举报