Bootstrap

jvm运行时数据区的理解

前言

对于java学习来说,若是只会实现一些简单的CRUD肯定是不够的,想要理解和深入学习java肯定还是药理解一下底层实现,jvm的机制和原理是很好的学习内容,所以读了《深入理解java虚拟机》。
读完后感觉还是不够,所以打算在写下笔记的时候巩固已学内容,和查漏补缺。

1、jvm的运行时数据区域

java的运行时数据区域:
jvm运行时数据区

1.1、程序计数器

记录正在执行的字节码指令地址。

1.2、java虚拟机栈

执行java方法时会除非黄健一个栈帧,栈帧拥有自己的局部变量表、操作数栈、动态链接、返回地址灯信息。
局部变量表:存储基本类型,引用类型,返回地址。
操作数栈:虚拟机执行指令时从这里弹出数据进行运算。

1.3、本地方法栈

与虚拟机栈类似,不过执行的是本地方法,有的虚拟机则直接把虚拟机栈和本地方法栈合二为一了(如Sun HotSpot虚拟机)。

1.4、java堆

这是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块区域,在虚拟机启动时创建。此内存的唯一作用是存储类的实例,几乎所有的对象实例都在此分配内存(这是由于标量替换、栈上分配等技术的出现,所以说几乎所有)。

java堆是垃圾回收器管理的主要区域,一般分为新生代和老年代,再细分可分为新生代中的eden区,From Survivor,To Survivor空间等,这样划分也是为了更好的进行垃圾回收。

新生代

此内存区域满时会触发minor GC,minor GC最常用的算法是复制收集算法。有数据表明新生代中的对象绝大部分是朝生夕死的,此时使用复制算法会浪费很多空间,为了更好的利用空间将新生代分为eden、To Survivor和From Survivor,将新生代的空间分为三个区域。当进行垃圾回收时,将一个survivor中的内容复制到另一个survivor中,为了延缓老年代的填充,同时避免空间碎片化,两个survivor交替使用,总有一个为空。
为了空间利用率,survivor的空间是比eden小的,但如果minor GC后存活的对象总内存大于survivor空间的大小(极端情况下全部存活),那么就需要老年代进行空间分配担保,把survivor无法容纳的对象直接放入老年代。但老年代进行担保也得有足够的空间,但所要放入的对象总内存是不知道的,所以只能根据之前每一次晋升到老年代对象内存大小的平均值,与老年代剩余空间进行比较来考虑是否需要full GC来让老年代腾出空间。这样只是一种折中的办法,要是某次担保存不下,则在进行full GC的同时还白白绕了圈子,可以通过虚拟机参数HandlerPromotionFailure来设置是否需要绕这个圈子。

1.5、方法区

存储加载的类信息,常量,静态变量,即时编译器编译后的代码,方法区是一个jvm规范,之前称为永久代,1.8后就没有永久代了,称为元空间,并把方法区移到了元空间中,位于本地内存(native memory)中,并把静态变量和常量池等放入了堆中。

本地内存(native memory)
  • 管理java heap的状态数据(用于GC)
  • JNI调用,也就是Native Stack
  • JIT(即时编译器)编译时使用Native Memory,并且JIT的输入(java字节码)和输出(可执行代码)也都是保存在本地内存中
  • NIO direct buffer。对于IBM JVM和HotSpot,都可以通过-XX:MaxDirectMemorySize来设置nio直接缓冲区的最大值。默认是64M,超过此值时会按照32M自动增大。
  • 对于IBM的JVM某些版本实现,类加载器和类信息都保存在本地内存中。
  • 本地内存的大小依赖于系统分配给进程的内存大小

1.8、常量池

运行时常量池和class文件常量池经常容易搞混,所以放在一起比较。

1.8.1、运行时常量池

平常说的常量池指的就是运行时常量池,是方法区的一部分,包括了字面量和(文本字符串,被声明为final的常量值,基本数据类型的值,等)符号引用(包括了类的结构和全限定名,字段名称和描述符,方法名称和描述符)。

1.8.2、class文件常量池

包含了字面量,符号引用,并在类加载时载入运行时常量池。

2、对象访问

jvm规范中只规定了reference是一个指向对象的引用,并没有定义此类型要如何实现。主流的访问方式有两种:使用句柄和直接指针。

  • 使用句柄方式则会在java堆中划分出一部分内存作为句柄池,引用中存储的是对象的句柄地址,而句柄中包含了对象实例数据和数据类型各自的具体信息,如下图所示。
    引用类型使用句柄示意图
  • 使用直接指针就需要考虑如何存放类型信息,如下图所示。 引用类型使用直接指针示意图
    使用这两种方式各有优势,使用句柄访问的最大好处是引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时对象移动是很常见的)时只需要改变句柄指向实例数据的指针即可,引用本身不需要修改。
    使用直接指针的最大好处则是访问速度更快,因为省区了一次指针定位的开销,对于HotSpot而言使用的是直接指针进行对象访问。

部分图和内容源自:
《深入理解java虚拟机》

;