Java虚拟机中堆和永久代,元空间,方法区的关系(JVM中的堆内存结构详解)

目录

前言

1.JVM的组织架构

2.堆

2.1 堆的基本概念

2.2 堆的组成部分

2.2.1堆的组成部分为什么是这样的?

2.3方法区的基本概念

2.3.1方法区和永久代以及元空间是什么关系呢?

2.3.2为什么永久代和元空间(二者都是方法区的实现)是逻辑上构成堆的?

2.3.3堆在物理上是由新生代+老年代构成的

3 总结

4 题外话(关于字符串常量池位置的变化)

5参考资料


前言

按顺序阅读本篇内容即可让你豁然开朗,有基础的可以直接看2.2 和2.3和3的内容

1 JVM的组织架构

JVM(Java虚拟机)的组织架构大概如下图所示:(此图片为JDK1.8及之后的JVM内存,因为本地内存中有元空间)

(图片摘自JVM面试题,54道Java虚拟机八股文(1.5万字51张手绘图),面渣逆袭必看👍 | 二哥的Java进阶之路)

一共分为大致四-五个区域:类加载器,运行时数据区,执行引擎以及本地方法接口(JNI)以及本地方法库

(本地内存并不属于JVM的一部分)

2 堆

2.1 堆的基本概念

Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代等;再细致一点有:Eden、Survivor、Old 等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。

2.2 堆的组成部分

首先讲到堆的组成部分,一定要分JDK的版本,因为JDK1.7和1.8之后堆的内存有变化!

先讲结论:(注意加粗部分:逻辑上!!!)

JDK1.7及之前的版本:堆在逻辑上是由新生代+老年代+永久代组成的

JDK1.8及之后的版本:堆在逻辑上是由新生代+老年代+元空间组成的

JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存

(所以在JVM组织架构那张图才会说这是JDK1.8之后的JVM组织机构)

下面是对上述两种情况的图例:(图片摘自Java内存区域详解(重点) | JavaGuide

新生代又可以分为Eden区,Survivor区(其中又可以分为FromSurvivor区S0 , ToSurvivor区S1,具体不多赘述,因为from和to区每次经历minorGC都是会变的)

Tenured是老年代,PermGen是永久代,MetaSpace是元空间

2.2.1堆的组成部分为什么是这样的?

关于堆内存结构的划分,《深入理解Java虚拟机》第三版是这么说的:

Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”)。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”“老年代”“永久代”“ Eden 空间”“From Survivor 空间”“To Survivor 空间”等名词,这些概念在本书后续章节中还会反复登场亮相,在这里笔者想先说明的是这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局,更不是《Java虚拟机规范》里对Java堆的进一步细致划分`。不少资料上经常写着类似于“Java虚拟机的堆内存分为新生代、老年代、永久代、Eden、Survivor……”这样的内容。在十年之前(以G1收集器的出现为分界),作为业界绝对主流的HotSpot虚拟机,它内部的垃圾收集器全部都基于“经典分代”来设计,需要新生代、老年代收集器搭配才能工作在这种背景下,上述说法还算是不会产生太大歧义。但是到了今天,垃圾收集器技术与十年前已不可同日而语,HotSpot里面也出现了不采用分代设计的新垃圾收集器,再按照上面的提法就有很多需要商榷的地方了

小总结:因为现代垃圾收集器大多基于分代收集理论而设计的,所以为了方便GC(垃圾回收)才把堆分成这几个部分(新生代,老年代,永久代/元空间)

2.3方法区的基本概念

方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说,在不同的虚拟机实现上,方法区的实现是不同的。

当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

下面是摘自《深入理解Java虚拟机》第三版的关于方法区的一段话:

(说到方法区,不得不提一下“永久代”这个概念,尤其是在JDK8以前,许多Java程序员都习惯在HotSpot虚拟机上开发、部署程序,很多人都更愿意把方法区称呼为“永久代”(Permanent Generation),或将两者混为一谈。本质上这两者并不是等价的,因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。)

2.3.1方法区和永久代以及元空间是什么关系呢?

方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。

(上述文字摘自JavaGuide的网站,上文有链接)

2.3.2为什么永久代和元空间(二者都是方法区的实现)是逻辑上构成堆的?

首先来看一下这两张图(分别为JDK1.7和1.8的JVM运行时数据区的划分):(本地内存并不属于JVM的组成部分之一)

区别在于:JDK1.7时,堆和方法区是并列存在于JVM之中的;JDK1.8的时候已经将永久代替换成了元空间,且将元空间放入了本地内存中

从图上可以看出:

在JDK1.7版本的时候,方法区是并列和堆存在于JVM之中的,并不是直接存在于堆里面,而在这个版本中方法区又是由永久代实现的,所以说堆在逻辑上是由新生代+老年代+永久代组成的

在JDK1.8版本的时候,永久代已经被元空间替代了,所以上图中已经没有永久代了,取而代之的是元空间,并且将元空间置于本地内存当中,在这个版本中方法区是由元空间实现的,所以说堆在逻辑上是由新生代+老年代+元空间组成的

2.3.3堆在物理上是由新生代+老年代构成的

下图摘自B站宋红康的JVM课程(68-堆的细分内存结构_哔哩哔哩_bilibili

这是JDK1.7版本的堆内存结构:通过设置如下VM option参数可以观察:

-Xms20m -Xmx20m -XX:+PrintGCDetails

将堆大小设置为20m,下图的新生代+老年代的内存大小刚好是我们设置的堆大小20m

6656K+13824K = 20480K

这是JDK1.8版本的堆内存结构:通过设置如下VM option参数可以观察:

-Xms20m -Xmx20m -XX:+PrintGCDetails

将堆大小设置为20m,下图的新生代+老年代的内存大小刚好是我们设置的堆大小20m

5632K+512K+512K+13824K = 20480K

3 总结

为了方便内存回收,大部分垃圾收集器都是基于分代的思想而设计的,而业内顶尖的HotSpot虚拟机为了方便管理方法区的内存,将垃圾收集器的分代思想延申至方法区,而方法区又是由永久代/元空间实现的,所以整个堆在逻辑上可以由新生代+老年代+永久代/元空间组成,而在物理内存上来看,堆还是由新生代+老年代来构成的

这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局

4 题外话(关于字符串常量池位置的变化)

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建

JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动了 Java 堆中

下图是摘自(Java内存区域详解(重点) | JavaGuide

(其实逻辑上都是在堆里面,因为永久代是在逻辑上堆的组成部分之一,但是在物理内存上并不是,具体参考上文所讲的内容)

5 参考资料

1.周志明《深入理解Java虚拟机》第三版

2.Java内存区域详解(重点) | JavaGuide

3.JVM面试题,54道Java虚拟机八股文(1.5万字51张手绘图),面渣逆袭必看👍 | 二哥的Java进阶之路

4. B站尚硅谷宋红康JVM教程 68-堆的细分内存结构_哔哩哔哩_bilibili 

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值