jdk8 HotSpot内存模型

时间:2021-11-10 17:18:47

概述

java的内存管理采用自动内存管理机制,这样就不需要程序员去写释放内存的代码,而且不容易出现内存泄漏问题。正是由于内存的申请和释放都交给了Java虚拟机,一旦出现内存泄漏和溢出问题时,在不了解Java虚拟机内存结构和自动管理机制的情况下,很难排查问题的所在。所以一个成熟的程序员和架构师,必须很好的掌握Java虚拟机的自动内存管理机制。

运行时数据区

jdk8 HotSpot内存模型

上图的虚拟机运行时数据区是Java虚拟机规范所规定的区域,不同的虚拟机有不同的实现。

程序计数器

程序计数器记录当前线程所执行的Java字节码的地址。当执行的是Native方法时,程序计数器为空。 程序计数器是JVM规范中唯一一个没有规定会导致OOMOutOfMemory)的区域。

Java虚拟机栈

Java虚拟机栈是Java方法执行的内存模型,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧(Stack Frame)存储 局部变量表操作数栈动态链接方法出口等信息。 会抛出 *Error OOM 异常。

本地方法栈

本地方法栈和虚拟机栈非常相似,不同的是虚拟机栈服务的是 Java 方法,而本地方法栈服务的是 Native 方法。HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。会抛出*ErrorOOM异常。

Java堆

Java堆用于存放对象实例:The heap is the runtime data area from which momory which memory for all class instances and arrays is allocated。是垃圾收集器管理的主要区域。可细分为:新生代和老年代;新生代又可分为Eden,from Survivor,to Survivor。会抛出*Error异常。

方法区

方法区存储虚拟机加载的类信息常量静态变量,即时编译器编译后的代码等数据。HotSpot中也称为永久代(Permanent Generation),(存储的是除了Java应用程序创建的对象之外,HotSpot虚拟机创建和使用的对象)。为什么称为永久代呢?? 各个地方说的都不清楚,查看官方文档,解释为:永久代中的对象并不是永久的,只是历史上被叫做永久代罢了。 In fact, the objects in it are not “permanent”, but that's what it has been called historically.
方法区在不同虚拟机中有不同的实现,HotSpot在1.7版本以前和1.7版本,1.7后都有变化。

jdk7版本以前的实现

jdk8 HotSpot内存模型

jdk7版本的改动是把字符串常量池移到了堆中。

jdk8 MetaSpace

jdk1.8中则把永久代给完全删除了,取而代之的是 

MetaSpace

 
jdk8 HotSpot内存模型
运行时常量池和静态变量都存储到了堆中,MetaSpace存储类的元数据,MetaSpace直接申请在本地内存中(Native memory),这样类的元数据分配只受本地内存大小的限制,OOM问题就不存在了除此之外,还有其他 很多好处:
  • Take advantage of Java Language Specification property : Classes and associated metadata lifetimes match class loader’s
  • Linear allocation only
  • No individual reclamation (except for RedefineClasses and class loading failure)
  • No GC scan or compaction
  • No relocation for metaspace objects

运行时常量池


运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中存储有常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。一般来说,处理保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性。Java语言并不要求常量一定只有编译期才能产生。

对象的内存布局


jdk8 HotSpot内存模型

Mark Word用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。
类型指针即对象指向它的类元数据的指针。并不是所有的虚拟机实现都必须在对象数据上保留类型指针(用句柄实现)。
实例数据    存储程序代码中定义的各种类型的字段内容,这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义的顺序的影响。HotSpot虚拟机默认的分配策略为longs/doubles,ints,shorts/chars,bytes/booleans,oop(Ordinary Object Pointers)。
对齐填充 并不是必然存在的,没用特别的含义。HotSpot的自动内存管理系统要求对象的起始地址必须是8字节的整数倍(对象的大小必须是8字节的整数倍)。


对象的创建

虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查符号引用代表的类是否已被加载,解析和初始化过。如果没有就先执行类的加载过程。接下来虚拟机为新对象分配内存(指针碰撞或空闲列表,Serial,ParNew等带Compact过程的收集器时采用指针碰撞,CMS这种基于Mark-Sweep缩放的收集器时通常采用空闲列表)。
处理并发是通过CAS配上失败重试的方式或者每个线程在堆上预先分配本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。
内存分配完成后,虚拟机将内存空间都初始化为零值(不包括对象头)。然后对对象头数据进行设置。
在完成以上工作后,从虚拟机的视角来看,一个新的对象已经产生。但从Java程序的视角来看,在执行完new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的访问定位

通过句柄访问对象

jdk8 HotSpot内存模型

通过直接指针访问对象

jdk8 HotSpot内存模型

这两种对象的访问方式各有优势,使用句柄来访问的最大好吃就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改
使用直接指针访问的最大好处就是速度快,它节省了异常指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多也是一项可观的执行成本。
HotSpot 是通过直接指针访问对象的方式进行对象访问的

参考文章 http://java-latte.blogspot.hk/2014/03/metaspace-in-java-8.html