美文网首页
JVM内存区域

JVM内存区域

作者: Lnstark | 来源:发表于2021-06-13 12:59 被阅读0次

Java的运行时数据区域包括线程私有的程序计数器,Java虚拟机栈和本地方法栈,线程共享的Java堆和方法区。


JVM内存区域.png

1.程序计数器

它可以看做是当前线程所执行的字节码的行号指示器。在多线程中,线程切换后需要恢复到正确的执行位置,因此每条线程都有一个独立的程序计数器。如果线程在执行一个Java方法,那么它记录的是正在执行的虚拟机字节码指令地址;如果执行的是本地方法,则计数器值是空。该区域也是唯一不会出现OutOfMemoryError的区域。

2.Java虚拟机栈

它的生命周期和线程相同,每个方法执行的时候,JVM会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用和执行完成对应着栈帧的入栈和出栈。局部变量表存放了基本数据类型(byte、char...)和对象引用。栈深过深如递归可能会造成StackOverflowError,如果创建了太多线程的话可能会造成OutOfMemorryError。

3.本地方法栈

本地方法栈的作用类似虚拟机栈,只是它是为本地方法服务的。同样会有StackOverflowError和OOM。

4.Java堆

堆是线程共享的内存区域,目的是用来存放 Java对象实例,也是垃圾回收的主要区域。主流的JVM的实现中它是可扩展的(通过-Xmx和-XMS设定)。当内存不足分配实例也无法扩展时,会抛出OOM。

5.方法区

  • 该区域主要用来存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码缓存等数据。jdk 8之前的永久代的数据在JDK 8里完全移到了元空间里。此区域会抛出OOM。

  • 运行时常量池是方法区的一部分,用于存放编译器产生的各种字面量与符号引用。String的intern()就是将新的常量放入常量池。它也会OOM。

6.直接内存

也叫堆外内存,在NIO中通过native方法直接分配,用DirectByteBuffer作为这块内存的引用,并可以直接进行操作,在有些场景下可以避免了Java堆和Native堆之间的数据拷贝,提高性能。该区域受机器的物理总内存限制,所以也会OOM。

7. 对象内存布局

Java对象由三部分组成:

  • 对象头(object header):包括了关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。Java对象和vm内部对象都有一个共同的对象头格式。
  • 实例数据(Instance Data):主要是存放类的数据信息,父类的信息,对象字段属性信息。
  • 对齐填充(Padding):为了字节对齐,填充的数据,不是必须的。
7.1 对象头

对象头包括Mark Word和Klass Pointer

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。

Mark Word在32位jvm里的存储内容(占4byte):


32位虚拟机.png

在64位jvm里的存储内容(占8byte):


64位虚拟机.png

Klass Pointer即类型指针,是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

7.2 实例数据

如果对象有属性字段,则这里会有数据信息。如果对象无属性字段,则这里就不会有数据。根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等;

7.3 对齐数据

所有的对象分配的字节总SIZE需要是8的倍数,如果前面的对象头和实例数据占用的总SIZE不满足要求,则通过对齐数据来填满。
为什么要对齐数据?字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址。

参考资料
《深入理解Java虚拟机(第3版)》
博客园-Java对象的内存布局

相关文章

网友评论

      本文标题:JVM内存区域

      本文链接:https://www.haomeiwen.com/subject/wfrqoktx.html