JDK运行时内存模型

目录

一、JDK内存模型

1.VM Stacks(虚拟机栈)

2.Native Method Stack(本地方法栈)

3.Program Counter Register(PC 寄存器)

4.Heap(堆内存)

5.Method Area(方法区)

5.1 PermGen(永久代)

5.2 Metaspace(元空间)

二、Java内存划分

1.堆内存与栈内存

2.JDK8以前的内存划分

3.JDK8现有内存划分

4.为什么要去掉永生代

5.元空间配置参数

5.1 MetaspaceSize

5.2 MaxMetaspaceSize

5.3 MinMetaspaceFreeRatio

5.4 MaxMetasaceFreeRatio

5.5 MaxMetaspaceExpansion

5.6 MinMetaspaceExpansion

一、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.为什么要去掉永生代

  1. 字符串存在永久代中,容易出现性能问题和内存溢出;
  2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出;
  3. 永久代会为 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)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值