jvm深入理解:运行时数据区知识点梳理

    技术2022-07-11  99

    2020年初,得知周志明老师出了深入理解java虚拟机第三版,之前一直听说这个系列的书籍是java中的圣经,所以买了本进行了研读,这里将重要知识点整理一下,再配合写一些自己的理解,以便以后的一个复习。

    出入:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

    一、运行时数据区

    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

    1.1 程序计数器

    程序计数器(Program Counter Register),占用内存小,字节码的行号指示器(虚拟机字节码指令地址)。 程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 线程私有。 同一时刻同一处理器,只会处理一条线程,线程切换通过程序计数器可恢复到正确的位置。 执行本地方法(jdk封装的调用底层的方法),计数值为空。

    1.2 Java虚拟机栈

    Java虚拟机栈(Java Virtual Machine Stack),线程私有,生命周期与线程相同。 拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

    局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。 数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小(变量槽数量)。一个变量槽大小由虚拟机自行决定。 这个内存区域规定了两类异常状况: 1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常; 2、如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

    1.3 本地方法栈

    本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。 Hot-Spot虚拟机直接就把本地方法栈和虚拟机栈合二为一。 也会抛出StackOverflowError和OutOfMemoryError。

    1.4 java堆

    Java堆(Java Heap)是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。垃圾回收的主要区域。 从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存。 如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

    1.5 方法区

    方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分。但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。 在JDK1.7以前HotSpot虚拟机使用永久代来实现方法区。 JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出。 JDK 8,终于完全废弃了永久代的概念,在本地内存中实现的元空间(Meta-space)来代替。 理解:元空间相当于方法区。逻辑上属于堆,物理上不属于。

    这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收有时又确实是必要的。如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

    1.6 运行时常量池

    运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 在Class文件结构中,最头的4个字节用于存储Megic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值。 常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:类和接口的全限定名、字段名称和描述符、方法名称和描述符。

    1.7 直接内存

    直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。 在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。 会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制。会出现OutOfMemoryError异常。

    Processed: 0.012, SQL: 9