查看原文
其他

通俗易懂,一文彻底理解JVM方法区

The following article is from 庆哥Java Author 庆哥小白

点击关注公众号,一周多次包邮送书

作者:庆哥小白

来源:庆哥Java(ID:ithuangqing)


本篇文章从Java内存结构在不同JDK版本上的一些差异讲起,重点聊聊方法区!


关于Java内存结构的差异,主要还是体现在jdk1.8和jdk1.7上,而且发生的主要变化在方法区上,在了解了什么是Java内存结构之后,我们知道,在jdk1.7上,Java内存结构主要包含以下5个部分:


  1. 堆内存

  2. Java虚拟机栈

  3. 本地方法栈

  4. 方法区

  5. 程序计数器


画个图就是这样的:


解读方法区


什么是方法区?


说的简单点就是Java内存结构中的一块内存区域,用来存放一些东西,存放什么呢?主要就是存放已经被虚拟机加载的类型信息,常量以及一些静态变量等信息,另外对于方法区,还存储着非常重要的一类东西,叫做“常量池”。


比如大家听说的类常量池,也即是Class常量池,然后还有字符串常量池以及运行时常量池!


然后再往上层去理解方法区的话,它则是《JVM规范》所定义的,就好比我们制造某个东西,需要按照一定的规范来制造,哪些东西必须有以及这个东西用来干什么,都是事先规范好的,举个简单的例子,比如我们现在要造一辆汽车,有哪些规范呢?


一些显而易见的,你得有轮子,一般还都是四个,轮子还要分为轮毂以及轮胎,这些就是规范,至于轮毂用什么材料,制造成什么样式等等,这些可以有制造商自己决定,所以也就会有各种各样的轮毂……


同理,这个虚拟机也是一样的,我们平常说Java虚拟机就如同在说汽车一样,我们知道汽车有很多品牌,有不同的制造商生产,那Java虚拟机也是一样的,也有很多种Java虚拟机,我们经常说的Java虚拟机,其实默认就是HotSpot虚拟机,这个虚拟机是目前用的最广的虚拟机,像OracleJDK和OpenJDK中使用的就都是HotSpot虚拟机!


但是我们要知道的是Java虚拟机绝对不等同于HotSpot虚拟机,就好比,汽车不等同于宝马,因为除了宝马还有奔驰和奥迪等,所以Java虚拟机除了HotSpot虚拟机之后还有BEA JRockit和IBM J9等其他虚拟机!

oracle的HotSpot虚拟,BEA System的JRockit虚拟机以及IBM公司的J9虚拟机,并称“三大商业Java虚拟机”

不过就目前而言,使用最多的还是HotSpot虚拟机,我们后面如果没有特殊说明,均是以HotSpot虚拟机为准!


知道了虚拟机的这些知识以后,我们再看方法区,就好比是组成汽车的轮毂,制造汽车轮毂是必须的,那制造Java虚拟机,方法区也是必须的,无论你是HotSpot虚拟机还是其他虚拟机,这个方法区都是必须有的!


在HotSpot虚拟机中,JDK7及之前的实现中,这个方法区都是有,而且直接就叫做方法区,但是在JDK8及之后,这个方法区就没有了,被移除了,但是前面不是说了嘛,这个方法区是必须的,是所有虚拟机都必须有的,咋办?

简单,重新造出一个去替代原有的方法区,就是重新实现,重新生成,所以在JDK1.8及之后方法区被移除,增加了一个叫做元空间的区域,这个元空间其实就是方法区的另一种实现,也可以看作原有的方法区升级改造,成了现在的元空间


就好比,现在制造汽车必须的轮毂可能在以后由于科技技术的发展有了新的制造方法,到那时候可能不叫做轮毂,改叫轮芯了也说不定,但是无论是轮毂还是轮芯其实本质还是那一样东西,只不过在不断升级着!


所以在JDK1.7及之前叫做方法区,但是在JDK1.8及之后就成了元空间!



永久代



想必大家一定也听说过永久代的说法,那这个永久代是啥呢?其实也很好理解,我们说,方法区其实就是制造虚拟机所必备的一样东西,就好比制造汽车必须要有轮毂,假如说现在有一本《汽车制造规范》,其中有说制造汽车的轮子需要先制造轮毂,然后还说了这个轮毂是怎样的,有什么用,该怎样造等最基本的规范,然后各个汽车制造商按照这个规范去制造轮毂!


只要百分百按照规范上的去制造,就能制造出一个最基本的轮毂,但是这样就没有啥特色啊,于是各个制造商就各自研究,在最基础的规范上增加了很多特色功能,于是就产生了各种各样的轮毂,当然,无论怎样制造,这些本质上都还是轮毂,但是各个制造商为了凸显自己的特色,可能就给自家制造出来的轮毂起了一些独特的名字!


OK,然后我们在回过头来看方法区,这个方法区就好比最基础的《JVM规范》上的方法区,于是各个虚拟机制造商在这个最基本的规范上去实现这个方法区,实现后的方法区在保有最基础功能之后还有很多特色功能,那这样不同的虚拟机制造商对自己实现的这个方法区可能就会给予不同的独特的命名!


那在HotSpot虚拟机制造当中,在JDK1.7及之前的对方法区的实现就是PermGen space,这个翻译过来就是“永久代空间”,但是我们知道,这个就是实现的方法区,所以早期也就直接叫做方法区了,一些对虚拟机比较熟悉的,知道对方法区的实现是PermGen space,所以也会叫做永久代,但是大家要明白的是PermGen space就是方法区!


到了JDK1.8及之后,这个PermGen space被移除了,也就是最初实现的永久代没了,重新实现的叫做Metaspace也就是元空间,其实我们知道,这也是方法区的实现,但是继续叫做方法区,似乎凸显不了升级与改造,也会让大家认为还是方法区,没啥改变,而叫做元空间,一听,哦,JDK1.8及之后有了重大升级!



元空间与永久代



到了这里,我们知道了什么是元空间以及永久代,其实都是对《JVM规范》中方法区的实现,但是元空间相比较永久代属于一次升级,那两者存在什么样的差异呢?


主要的差异就是这么一句话:


元空间不在虚拟机设置的内存中,而是使用本地内存


什么意思呢?我们先来看这么一句话:


在JDK1.7及之前,HotSpot虚拟机中的方法区实现是永久代,而且,永久代与堆是相互隔离的,但是,他们使用的是连续的物理内存


什么意思呢?看之前我们画的图:

我们知道,此时的方法区就是永久代,而更加准确的画法应该是这样的:

也就是堆内存和方法区也即永久代是使用的连续的物理内存,但是他们之间是互相隔离的,所以对于永久代也有一种叫法叫做“非堆”!


基于此,我们再深入的去分析一下,在Java内存结构中(也即是Java运行时数据区),堆内存是最大的一块内存区域,我们多少也听说过,堆内存中也有着分类,比如分为新生代,老年代等,但是大家也要清楚,这样的描述是在虚拟机采用的是“分代”垃圾收集器的前提下!


作为使用最多的HotSpot虚拟机,它内部使用的垃圾收集器,基本都是使用“经典分代”来设计的,所以对于堆内存才会分为新生代以及老年代这些,基于此,大家应该会想到永久代,的确,在以永久代为方法区实现的情况下,永久代其实也是隶属于堆内存的!


什么意思呢?方法区是虚拟机必须要有的,在JDK1.7及之前它的实现就是永久代,那永久代这块内存区域是怎么来的呢?其实它就是堆内存中的一块区域,也就是说,从堆内存中拿出一块内存区域,将其设计成永久代,也就是所谓的方法区!


所以上面我们才说,永久代也称为非堆,虽然隶属于堆,但是他们之间又是相互隔离的!


我们之前说过,方法区主要用来存放被虚拟机加载的类型信息,常量以及静态变量等,除此之外,还有很重要的一个就是常量池(这个被称为运行时常量池,主要存放的内容是通过类加载得到的Class文件中的类常量池)!


也就是说,运行时常量池其实是方法区的一部分,Class文件中含有一个类常量池(Class文件常量池),这部分内容在类加载之后会存放到运行时常量池!



特别注意


其实真正的变化是从JDK1.7就开始了,在JDK1.7之前,虚拟机加载的类型信息,常量以及静态变量和常量池等是正儿八经的都在永久代上存放的,但是在JDK1.7就开始发生了变化!


首先就是字符串常量池已经从永久代上转移到了堆上,但是这个时候永久代依然存在于JDK1.7之中,不过已经慢慢在削弱它的作用了,


到了JDK1.8就彻底移除了永久代,而元空间则正式登场,此时没有了永久代,之前还剩的存在于永久代上的比如类常量池,运行时常量池等就直接保存在了元空间,而此前转移到堆中的字符串常量池并没有重新转移到元空间,而是继续保留在了堆内存中!


OK,看到了这里,大家再看永久代和元空间的主要区别:


元空间不在虚拟机设置的内存中,而是使用本地内存


我们再看JDK1.7的内存结构:


此时这5个部分组成了Java的运行时数据区,也就是JVM内存结构由这五部分组成,但是在JDK1.8中,永久代移除,元空间登场,此时的JVM内存结构就产生了变化,首先就是没有方法区了,取而代之的则是元空间,此时的内存结构就是这样的:


这个该怎么理解呢?我们之前说了元空间与永久代的主要区别就是:


元空间不在虚拟机设置的内存中,而是使用本地内存


但是这句话看起来很让人费解,首先什么是虚拟机设置的内存?什么又是本地内存?我觉得这样的描述更加好理解:


永久代与堆内存存在于连续的物理内存上,可以看作是从堆内存中拿出一部分内存作为永久代,但是两者又是互相隔离的,所以永久代称为非堆,此时永久代的大小受限于堆内存的大小,因为永久代是从堆内存拿的空间,逻辑上是与堆内存连在一起的,但是元空间则不是与堆内存相连的连续的物理内存,而是另外单独的一个内存区域,这个内存区域大大小取决于电脑的内存大小!


什么意思呢?也就是说,之前的永久代隶属于堆内存,可以看作是先开辟了堆内存,然后在堆内存中开辟永久代,然后将两者隔离,但是现在元空间是脱离堆内存,直接另外开辟空间相当于和堆内存同一层级了!


至于为什么说元空间是使用本地内存,这个本地内存其实就是电脑的内存,像Java内存结构中的这几个部分都是用的电脑内存,可以说都是本地内存,只不过单独命名叫做堆或者栈,或者程序计数器啥的,而现在元空间就是这么一块区域!


这里有个难点就是要理解永久代的实现是在堆内存的基础上,然后在永久代中实现的方法区!也就是说,我们之前一直见到的方法区是这样的:

但其实这样是不准确的,正确的应该是这样:

也就是方法区和堆内存是连续的物理内存,是没有分开的,而到了元空间才是分开的,与堆内存是不相连的:

为了便于理解,大家就可以间接认为,永久代是堆内存的一部分,而元空间则是独立于堆内存的单独部内存区域!



总结



其实关于JVM的学习,是有难度的,尤其是各种纷繁复杂的概念,很多东西我们没有一定的知识积累,理解起来着实很费劲,所以前期学习我们则是怎么好理解怎么来,当然,这回牺牲掉一定的准确性,但是这样去学习一定程度上对我们来说是有好处的,因为知识的学习需要循序渐进!


啥意思呢?比如上面我们说了,永久代就是方法区,实际上这样的说法是不准确的,也就是说,方法区并不等同于永久代,更加准确的说法是使用永久代来实现方法区而已,包括元空间也是一样,并不能说元空间就是方法区,也只能说是使用元空间来实现方法区,但是前期我们学习,不可能所有的东西都学习得非常透彻,只能说是慢慢来!


因此,我们前期对于这块的学习,暂且可以记住永久代就是方法区,而元空间也是方法区的实现,但是也要了解,本质上两者不能等价,更深层次的原因我们随后后续的学习就会不断揭开!


·················END·················

推荐阅读

• Mysql的索引为什么使用B+树而不使用跳表?• 20个示例!Java8 Stream用法,从此告别shi山(垃圾代码)• 一图胜千言,超形象图解NumPy教程!• 都是同样条件的mysql select语句,为什么读到的内容却不一样?• fencedframe 可以替代 iframe 吗?• 如何用Python发送通知到微信?• Java单点登录系统,用几张漫画就解释了 。。。

👇更多内容请点击👇

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存