理解虚拟机规范和HotSpot对其的实现

引言

之前写过一篇JVM’内存区域的博文,最近重新学习并理解JVM内存区域,看了相关的官方文档和《深入理解java虚拟机》,发现之前对此的理解和认识有些误区,比如JDK1.8和1.7以及之前内存区域划分的区别,相信大部分人都会认为是从MethodArea(方法区)到Meta-Space(元空间)的转变,但是其实虚拟机规范中对内存区域的划分和不同虚拟机对虚拟机规范中内存区域划分的实现是两个概念和完全不同的事情,他们不是相等的关系,如果非要给两者下一个定义:两者属于规范和具体实现的关系,类似于类和对象的关系,类好比制定的规则,对象则在规则下选择自己自由的方式去实现。虚拟机规范是一个规则、规范,定义了虚拟机必须要去遵守、实现的规则。而虚拟机要去实现这几部分,但是具体通过什么方式去实现虚拟机规范中的内存区域划分模型,由虚拟机按照自己的方式去实现。上边提到的mate-space是HotSpot、Jrockit、IBM J9等虚拟机对虚拟机规范中方法区的具体实现。对HotSpot虚拟机而言,在JDK1.8之前,Hotspot虚拟机使用永久代来实现方法区,所以有了后面大部分人认为JDK1.8后,废除了永久代,变为Mate -Space来,原因是大部分人都是在HotSpot虚拟机上开发和部署java程序,而这一变化是HotSpot虚拟机针对方法区的实现由永久代变为Mate-Space而已,所以大多数人对两者的概念并不清晰,或者说对虚拟机规范和具体的虚拟机对虚拟机规范中内存区域模型的实现这两者的关系不清晰。本博客将详细介绍这两部分内容和两者的关系。

​​​​​​

虚拟机规范中对JVM内存区域的划分模型

虚拟机规范运行时数据区域

虚拟机规范中规定java虚拟机运行时数据区域分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器。谈到运行时数据区域不得不放出下面的图(图1),但是有些人把这个图作为jdk1.7内存区域划分模型图,这是不对的,因为这个图是虚拟机规范中定义的java虚拟机运行时数据区域划分模型,里面提到的区域由不同的虚拟机自由去实现,而此规范只是说虚拟机需要实现以下几个区域,但是具体怎么实现,虚拟机开发者可以自由去按照自己的方式去实现

方法区

方法区随虚拟机的启动而创建;与虚拟机拥有一样的生命周期,主要存放了被虚拟机加载的类信息,常量,静态变量、即时编译器编译后的代码缓存等数据。会产生OOM,属于线程共享的数据区域。

运行时常量池,在方法区内有一个叫常量池的的东西,里面存储了编译期生成的字面常量、符号引用,在类加载后放到方法区内,需要注意的是在运行时也可以将新的常量放入里面。

堆随虚拟机的启动而创建;与虚拟机拥有一样的生命周期,主要存放了java对象,是对象分配的主要区域。但是需要注意一点的是,堆中可以存放每个线程私有的对象,即:堆中可以划分出多个线程私有的对象分配缓冲区TLAB(Thread Local Allocation Buffer)。堆可以被虚拟机实现为固定大小的,也可以实现成可扩展的。大部分虚拟机都是设计为可扩展的。这部分区域会产生OOM,属于线程共有的。

程序计数器

随线程的创建而创建;与线程拥有一样的生命周期记录了每一个线程执行的字节码行号,保证了多线程程序运行切换中,能从正确的地方开始执行。当线程执行java方法时,程序器记录的是正在执行字节码指令的地址,当执行的是本地方法时,为空。程序计数器不会发生OOM,属于线程私有的。

虚拟机栈

随线程的创建而创建,与线程拥有一样的生命周期。描述的是java方法执行的线程内存模型;里面主要存放的是栈帧,每一个栈帧里面主要存储了局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存储了基本数据类型、对象的引用、ReturnAddress类型。上面所说的数据类型在局部变量表里面以变量槽进行存储。局部变量表所需要的空间在编译时分配。当线程超过了虚拟机栈规定的最大深度时会抛出StackOverflow异常,当栈空间不足还活着栈空间扩展不足时(根据具体的虚拟机实现虚拟机栈的方式为准),会抛出OOM。属于线程私有的。该部分属于线程私有的。

本地方法栈

本地方法栈为java程序需要执行的本地方法服务。具体怎么去实现该部分,由虚拟自己自由选择,可以直接使用本地方法栈,不会将本地方法栈加入到虚拟机中,也可以将本地方法栈与虚拟机栈放在一起(HotSpot虚拟机)实现,在虚拟机中分配本地方法空间。会抛出 stackOverFlow和OOM异常。该部分线程私有的。

以上就是对虚拟机内存区域划分模型的详细说明,至于虚拟机怎么去实现上述部分,由虚拟机自己决定。下面看一下HotSpot虚拟机对虚拟机规范中各内存区域的实现

不同虚拟机的实现以hotspot为例

不同虚拟机对虚拟机规范中内存区域划分的实现是不同的。HotSpot对线程私有的虚拟机栈和本地方法栈放在一起实现,共用一个线程栈、程序计数器每个线程拥有一个;而对堆和方法区的实现在jdk1.8及其之后发生了较大变化,大家都知道堆是虚拟机中最大的一块区域,对象的分配和回收主要在堆上进行,而堆中如果一直分配对象不回收的话就会产生OOM,所以谈到堆的实现必然涉及到垃圾回收,目前主要有分代收集思想和全域收集思想,而HotSpot虚拟机则采用分代收集思想,HotSpot虚拟机中在jdk1.8以前将堆实现为年轻代(又分为eden、survivor from、survivor to)、老年代、将永久代作为方法区的实现。在jdl1.8及其之后,堆实现为年轻代、老年代,将方法区实现为mate-space,mate-space则为本地内存,值得一提的是matespace在逻辑上讲也属于堆的一部分,但是为了与虚拟机中的堆作为区分,大部分人又叫他“非堆”。

以上介绍了虚拟机规范运行时数据区域划分模型和HotSpot虚拟机对规范的实现,并说明了jdk1.8及其之后的实现区别。希望大家学习了后真正了解两者的区别和联系。不要在混为一谈。

参考资料:java虚拟机规范官方文档

                《深入理解java虚拟机》

                封面图片来自https://www.oracle.com/webfolder/technetwork/tutorials/mooc/JVM_Troubleshooting/week1/lesson1.pdf

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值