天天看点

JVM学习笔记(二)JVM运行时内存模型

需求很大程度上决定学习导向,可能对于很多人来说之所以学习JVM是因为需要在部署到JVM上的运用开发中解决各种各样的问题(详见上一篇博客:JVM学习笔记(一) JVM学习回顾–概述与思考)。我们首先来学习了解JVM的运行时内存模型。譬如java程序,在运行过程中总是伴随着对象内存的不断创建与销毁,而在对象的生命周期中亦伴随着内存的分配与回收。这时候就不得不提到JVM的自动内存回收了;根据生活中的经验,对于暂时失去作用、不再需要且受限于存储空间的物品,我们会考虑将其丢弃,也就是扔到垃圾箱进行回收。JVM同样如此,操作系统分配给JVM的存储资源总是有限的,JVM也会根据当前持有的资源进行权衡分配。JVM对于内存回收简单干脆的命名为Garbage Collection(GC),国内见得比较多的也是直译为“垃圾回收”或称GC,在此我们也简称GC吧。

本人一开始对于JVM内存回收是有很大疑问的;这里的内存是指哪门子内存呢,GC是对那部分区域进行操作的呢,说好的栈内存与堆内存又是怎么划分的?

嗯!首先来了解一下JVM运行时内存模型吧(在此根据Java SE 7规范),JVM运行时内存分为如下五部分:

JVM学习笔记(二)JVM运行时内存模型

1、Java堆

在大多数场景下作为JVM管理分配内存中最大的区域,此处作为对象分配存放的主要场所,也是GC操作最频繁之处。因为大多数GC都采用分代(根据对象能够逃过GC存活下来的次数划分)回收机制将对象生命周期分为新生代阶段,老年代阶段。其中将年轻代进一步细分为 Eden区、From Survivor区(S1,个人理解为新生代向老年代过度区)、To Survivor区(S2,具体见这位大佬的博客:关于JVM中Eden区、Survivor from区和Survivor to区的理解)。其实在java程序中大多数对象都是“朝生夕灭”的,大多数对象活不到老年代阶段(具体在下一篇博客中分析(挖个坑))。此外Java堆内存在逻辑上连续分配,在物理上可呈现离散分配(想想链表的空间分配机制)并且是可灵活拓展的(在配置资源允许的情况下)。因为堆空间的这种灵活性,其也支持通过参数(-Xmx,-Xms)设置来进行控制,当最大分配堆内存(Xmx)与最小分配堆内存(Xms)相同时将在一定程度上提高JVM性能(避免了内存回收时的堆内存收缩以及动态扩展所需的运算),但这需要更多的操作系统资源占用。

2、Java虚拟机栈

作为线程私有内存区域,存储了本线程运行时的局部变量并且提供栈帧存储区。其中局部变量表存储了运行期可知的基本数据类型以及引用类型,64位数据类型(double、long)会占用两个局部变量空间(slot,详见这位大佬博客:运行时栈帧结构-局部变量表-探究slot复用 )。栈帧便是对应程序运行中的程序栈相关概念了,在线程启动运行中调用的方法都会在栈帧中留下进出栈记录(简单理解为方法调用时进栈;调用返回时出栈记录表)。当方法调用深度超出JVM上限时将会抛出StackOverFlowError异常,虚拟机栈可拓展,在空间资源受限下进行内存申请时将会产生OutOfMemoryError(OOM)异常。

3、本地方法栈

和java虚拟机栈类似,此处多了Native服务,相对少了为虚拟机提供的字节码服务。

注意:在我们用的比较多的HotSpot VM中将上述两种栈合并了。Oracle官方发布的java11运用的依旧是Hotspot,如下图:

JVM学习笔记(二)JVM运行时内存模型

4、方法区

作为线程共享的内存区域,存储了JVM加载的类信息、JIT编译后的代码、常量以及静态变量等,我们所说的运行时常量池(关于常量池分类详见这位大佬的总结:字符串常量池、class常量池和运行时常量池)便是存在此处。方法区存储的都是一些可以长时间存活的数据,GC操作在此区域并不活跃,早期在HotSpot设计中方法区也被称之为永久代(为了与堆内存GC机制统一,免得再去开发一套针对方法区的GC机制,但效果并不好,java8开始已经废弃这一区域而开发出了元空间),但并不代表方法区数据可以一直存活。在满足条件的情况下也会被回收(常量池回收、类卸载),在此处进行内存回收时将会付出更大的代价。同样的,方法区在无法进行内存分配时(达到-XX:MaxPermSize上限,@Deprecated since java8)将抛出OutOfMemoryError异常。

关于永久代与元空间的纠葛见这位大佬的总结:永久代(PermGen)和元空间(Metaspace)的区别)

5、程序计数器

线程私有的一块较小内存空间,实现了线程执行时的取指跳转以及各种程序结构功能,与操作系统PC类似。其为线程切换后的现场保存与恢复提供保障,即由于PC的存在使得程序中断后继续执行时能够回到正确的指令地址。需要注意的是当执行Native方法时,PC值为Undefined。这也是唯一一个在Java虚拟机规范中没有规定任何OOM情况的区域(截止到java7看来是这样)。

除了上述五大传统分类外,还有一个叫做直接内存的区域,直接内存不属于JVM直接定义管理的内存区域,其不受JVM内存分配限制(但受限于操作系统)。此区域也频繁被JVM程序使用,如NIO活动于直接内存中(详见这位大佬的博客:JVM直接内存)。

总结

JVM内存模型除了在面试中比较常见外,在实际的开发运用中也有着举足轻重的地位,学习了解了这部分知识让我们粗略知道自己编写的程序运行在怎样的环境下。同样JVM的学习理解也是从初级Java开发者迈向高级程序员的必经之路。

参考文献

1.《深入理解Java虚拟机》 JVM高级特性与最佳实践 周志明

2.关于JVM中Eden区、Survivor from区和Survivor to区的理解

3.运行时栈帧结构-局部变量表-探究slot复用

4.字符串常量池、class常量池和运行时常量池

5.永久代(PermGen)和元空间(Metaspace)的区别

6.JVM直接内存