JDK运行时内存模型
目录
3.Program Counter Register(PC 寄存器)
一、JDK内存模型
根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
1.VM Stacks(虚拟机栈)
每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值。
虚拟机栈除了上述错误外,还有另一种错误,那就是当申请不到空间时,会抛出 OutOfMemoryError。
2.Native Method Stack(本地方法栈)
与虚拟机栈类似,区别是虚拟机栈执行java方法,本地方法站执行native方法。在虚拟机规范中对本地方法栈中方法使用的语言、使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它。本地方法栈也可以抛出StackOverflowError和OutOfMemoryError。
3.Program Counter Register(PC 寄存器)
也叫程序计数器。可以看成是当前线程所执行的字节码的行号指示器。
在任何一个确定的时刻,一个处理器(对于多内核来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,我们称这类内存区域为“线程私有”内存。
倘若当前线程执行的是 JAVA 的方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native 方法,则PC寄存器中为空。
4.Heap(堆内存)
堆是JVM内存占用最大,管理最复杂的一个区域,是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。1.8后,字符串常量池从永久代中剥离出来,存放在堆中。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。
5.Method Area(方法区)
也是所有线程共享。主要用于存储类的信息、常量池、静态变量、及时编译器编译后的代码等数据。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 方法区以JDK8为分界点,分别有两种实现。JDK8以前的永久代,JDK8以及以后的元空间。
5.1 PermGen(永久代)
绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。
5.2 Metaspace(元空间)
其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
二、Java内存划分
1.堆内存与栈内存
JVM中内存通常划分为两个部分,分别为堆内存与栈内存:
- 栈内存:和Java类中的方法密切相关,它会存储局部变量以及方法调用的中间结果及返回值。Java中的每个线程都有自己专属的栈内存,这是别的线程无法访问的。可以通过JVM选项-Xss来进行调整
- 堆内存:是Java应用程序中实例化的每个对象所存储的地方。它由所有线程共享。当堆耗尽的时候,JVM会抛出java.lang.OutOfMemoryError 异常。堆的大小可以通过JVM选项-Xms和-Xmx来进行调整。
2.JDK8以前的内存划分
在JDK7以及其前期的JDK版本号中。堆内存通常被分为三块区域:新生代内存(young generation)、老生代(old generation)、永生代(Permanent Generation for VM Matedata),例如以下图:
上图所示堆内存被分为:
- Eden区:新对象或者生命周期很短的对象会存储在这个区域中,这个区的大小可以通过-XX:NewSize和-XX:MaxNewSize参数来调整。新生代GC(垃圾回收器)会清理这一区域。
- Survivor区:那些历经了Eden区的垃圾回收仍能存活下来的依旧存在引用的对象会待在这个区域。这个区的大小可以由JVM参数-XX:SurvivorRatio来进行调节。
- Old Generation老年代:那些在历经了Eden区和Survivor区的多次GC后仍然存活下来的对象会存储在这个区里。这个区会由一个特殊的垃圾回收器来负责。年老代中的对象的回收是由老年代的GC(major GC)来进行的。
- 永生代:存放着对象的方法、变量等元数据信息。如果永生代内存不够,我们就会得到例如以下错误:java.lang.OutOfMemoryError: PermGen
3.JDK8现有内存划分
Java8中把存放元数据中的永生代内存从堆内存中移到了本地内存(native memory)中,Java8中JVM堆内存结构就变成了以下所示:
这样永生代内存就不再占用堆内存。它能够通过自己主动增长来避免JDK7以及前期版本号中常见的永生代内存错误(java.lang.OutOfMemoryError: PermGen)。随着Java8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。
Java8也提供了一个新的设置Matespace内存大小的參数。通过这个參数能够设置Matespace内存大小,这样我们能够依据自己项目的实际情况,避免过度浪费本地内存,达到有效利用:
-XX:MaxMetaspaceSize=128m 设置最大的元内存空间128m。如果你设置的元内存空间过小,你的应用程序可能得到下面错误: java.lang.OutOfMemoryError: Metadata space
4.为什么要去掉永生代
- 字符串存在永久代中,容易出现性能问题和内存溢出;
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出;
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
以下是参照官方说明:
移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
类的元数据信息转移到Metaspace的原因是PermGen很难调整。PermGen中类的元数据信息在每次FullGC的时候可能会被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。
字符串存在永久代中,容易出现性能问题和内存溢出。
元空间是方法区的在HotSpot Jvm 中的实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,需要配置参数。
5.元空间配置参数
5.1 MetaspaceSize
初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数
5.2 MaxMetaspaceSize
限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。
5.3 MinMetaspaceFreeRatio
当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
5.4 MaxMetasaceFreeRatio
当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。
5.5 MaxMetaspaceExpansion
Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。
5.6 MinMetaspaceExpansion
Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约为330KB)。