JVM 内存模型
根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
线程私有:虚拟机栈,本地方法栈,程序计数器。
线程共享:堆,方法区。
JDK 1.7 中的JVM内存结构图
堆和方法区连在了一起,但这并不能说堆和方法区是一起的,它们在逻辑上依旧是分开的。但在物理上来说,它们又是连续的一块内存。也就是说,方法区和Eden和老年代是连续的。
程序计数器(Program Counter Register)
概念:当前线程所执行的字节码的行号指示器。
功能:在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
注:唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈
概念
:虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表
:存放了编译期可知的基本数据类型(boolean
、byte
、char
、short
、int
、float
、long
、double
)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和一条指向了字节码指令的地址(returnAddress类型)。
异常
:
StackOverflowError:线程请求的栈的深度大于虚拟机所允许的深度。
OutOfMemoryError: 虚拟机栈动态扩展时,无法申请到足够的内存。
本地方法栈
与虚拟机栈的作用非常相似,主要与虚拟机用到的 Native 方法相关。
Java堆
是java虚拟机所管理内存中最大的一块,是线程共享的一块内存区域。几乎所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器(https://www.jianshu.com/p/11b91da0b0a0)的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术,导致所有的对象都分配在堆上也不是那么绝对了。
垃圾回收:java堆是垃圾收集器管理的主要区域,也称作GC堆。
方法区
概念:方法区是线程共享的内存区域,存储已被虚拟机加载的类信息
、常量
、静态变量
、即时编译器编译后的代码
等数据。在java虚拟机规范中,方法区描述为堆的一个逻辑部分,为了与java堆区分开来,方法区也称作Non-Heap(非堆)。
对于习惯了在HotSpot虚拟机上开发、部署的程序员来说,很多都愿意将方法区称作永久代(Permanet Generation,也称PermGen)。
本质上来讲两者并不等价
,仅因为Hotspot将GC分代扩展至方法区,或者说使用永久代来实现方法区
。在其他虚拟机上是没有永久代的概念的。也就是说方法区是规范
,永久代是Hotspot针对该规范进行的实现
。
永久代和堆是相互隔离的,但它们使用的物理内存是连续的。
永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集
。
但在Java7中永久代中存储的部分数据已经开始转移到Java Heap或Native Memory中了。比如,符号引用(Symbols)转移到了Native Memory;字符串常量池(interned strings)转移到了Java Heap;类的静态变量(class statics)转移到了Java Heap。
在Java8中,Hotspot取消了永久代。永久代的参数-XX:PermSize和-XX:MaxPermSize也随之失效。
元空间(Metaspace)
对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?当然不是,方法区只是一个规范,只不过它的实现变了。
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
本地内存(Native memory),也称为C-Heap,是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。
针对Java8的调整,我们再次对内存结构图进行调整。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
元空间存在于本地内存,意味着只要本地内存足够,它不会出现像永久代中“java.lang.OutOfMemoryError: PermGen space”这种错误。看上图中的方法区,是不是“膨胀”了。
默认情况下元空间是可以无限使用本地内存的,但为了不让它如此膨胀,JVM同样提供了参数来限制它使用的使用。
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
总结
为什么要做这个转换?
1、字符串存在永久代中,容易出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4、Oracle 可能会将HotSpot 与 JRockit 合二为一。
转自:
1.https://www.cnblogs.com/secbro/p/11718987.html
2.https://www.cnblogs.com/paddix/p/5309550.html
参考:《深入理解java虚拟机》