JVM5:JVM内存模型与运行时数据区的关系,堆为什么分区,分代年龄,Young区划分,Survivor区为什么分为S0和S1,如何理解各种GC:Partial GC、Full GC、Young GC

JVM内存模型与JVM运行时数据区的关系

JVM内存模型就是对于JVM运行时数据区这一规范的落地,而所谓的内存模型,都是基于SUN公司的hotsport虚拟机来进行展开的。可以这样理解,JVM运行时数据区是一种规范,而JVM内存模式是对该规范的实现。
请添加图片描述
如上图运行时数据区的结构,左面是和进程相关的内容,也就是线程共享的;右边是和线程相关的内容,与线程的生命周期相关,所以它的生命周期较短,它们是线程私有的,右侧的数据结构,就是类文件写好即.java文件,编译生成了对应的字节码文件,那么它的结构大小基本上就已经确定了,而且由于它们存在的时间不会很长,所以这一部分内存不需要重点关注。

JVM内存模型图解

重点关注生命周期较长的区域的两块内容,即左侧的方法区和堆这两块规范区域的落地情况。它的规范的落地就是类似下面的图示,JVM分代思想。
请添加图片描述
一块是非堆区,一块是堆区。
堆区分为两大块,一个是Old区,一个是Young区。
Young区分为两大块,一个是Survivor区(S0+S1),也叫做S区,一块是Eden区,S0和S1一样大,也可以叫From和To。

方法区是一个线程共享模型,是堆的一部分,有个别名,叫做“非堆”,目的是为了跟我们真正的Java堆区分开来。非堆之所以叫非堆,是因为它是堆逻辑的一部分,仅仅只是逻辑,方法区实际的落地是分为两块。有一部分是在JVM的堆内存当中进行一个实现,包括字符串常量池,静态变量,包括类信息是放在直接内存中的。方法区在内存不足的情况下,会抛出OOM。
注意:Java官网对于方法区的规范和方法区的具体实现并不一样,因为这仅仅只是一个规范,是一个概念(存放方法的信息),而真正的实现会随着jdk的版本而进行优化,实现是会改名的,每个版本叫法可能并不一样。

方法区各版本实现

  • jdk6: 方法区在jdk6中的实现是叫做永久代
  • jdk7: 方法区在jdk7中的实现中叫做永久代(去部分永久代),也叫做Perm Space,jdk7中做了一部分去永久代的操作,Perm Space使用的是JVM自己的内存(是JVM自己的内存,由JVM从系统中抢占过来的内存)
  • jdk8: 方法区在jdk8中的实现是叫做元空间,也叫做元数据区,也叫做叫做Meta Space,Meta Space使用的直存(直接内存,也就是我们系统的可用内存)

jdk1.6,1.7,1.8版本方法区具体实现变化、为什么jdk1.8移除了永久代

堆为什么分区?

如果不对堆内存进行分区划分的话,在堆中不断的进行分配对象,最终会占满内存,这个时候如果说内存不够用了,那么就会去触发垃圾回收,当然不会等到全部占满之后再去进行垃圾回收,快满了就去扔掉,而不是放满,一定是到了一个不能容忍的界限之后,比如说60%的空间被对象占满的时候,这个时候就会回收掉一部分对象。而如果进行回收的时候,针对的是一整块内存,需要每次对整个内存空间去做一次扫描,扫描去确认和标记对象是否是垃圾,这样操作起来不方便。而对象有一个特殊的属性名叫作生命周期,对象会随着它生命周期的结束而消亡,大量测试表明对象在堆内存当中,大多数对象它的生命周期都很短,因为客户端的请求落到服务器上,走到对应的方法上之后,比如这个方法里面有创建对象,即new Object(),这个时候,方法一结束,这个对象相应的也就消亡了,所以很多对象都是这种,生命周期很短。

而正是因为有堆内存中大多数对象的生命周期都很短这样一个理论支撑,这个时候,就认为不需要扫描整个堆内存,而是将这一整块内存进行分区,按照对象存在的生命周期将其分成两个区域,生命周期长的分在Old区,生命周期短的分在Young区。比如刚进来的对象在Young区,垃圾回收开始扫描Young区,在经历了几次垃圾回收之后,仍然存在的对象,就认为属于老对象了,将其转而放置在Old区。

分代年龄

根据对象的生命周期对内存去做了一个这样的划分,而整个对象的存货时间的长短是按照垃圾回收的一个频率来计算的,比如说说刚进来的对象分代年龄为0,经过一次垃圾回收之后,分代年龄+1变成了1。这也是为什么Java对象内存布局中对象头中的Mark Word中会有分代年龄的标记位。
请添加图片描述
对于分代年龄的理解:每经过一次GC,分代年龄就会+1。

Young区和Old区如何转换?
对象年龄的大小,取决于垃圾回收的次数,那么新老年代必然中间会有一个标准,或者叫阈值、分界线。
比如说刚进来的对象分代年龄为0,经过一次垃圾回收之后,分代年龄+1变为1。那么分代年龄肯定不会无限增长,一定是有一个临界点的存在。分代年龄多大才会去到老年代?
默认情况是15,也就是经历15次GC,15GC之后对象仍然存活,在第16次GC的时候,就去往了老年代。

为什么分代年龄默认是15?
Java对象内存布局中,它存分代年龄的时候,使用的是4个2进制位,那么取值范围就是0000 ~ 1111,也就是0 ~ 15。因为进入老年代的标准就是足够老,而在范围区间内,通常最大值可以直观的表示临界值。
当然有的人会有疑问,15次是否太大?为了满足开发者的需求,JVM是将这个分代年龄作为参数,可以自定义设置。

Young区划分

Young区为什么要划分?内存空间不连续问题

Young GC的过程中会产生新的问题,内存空间的不连续问题。如下图:
请添加图片描述
这个时候会发现由于由于内存空间的不连续性,没有办法将B1放入。这种现象就叫做内存空间的不连续性,也可以称之为空间碎片

也就是说,这样的空间碎片会导致在整个内存中间,一旦有一个较大的对象进来,明明大小上满足,但是由于空间碎片的存在,没有办法进行存放。所以JVM是如何解决的?
这个时候,再对Young区再进行一个设置,增加Eden区和Survivor区。

Eden区

Eden区:伊甸园,是地上的乐园,根据《圣经·旧约·创世纪》记载,神―耶和华照自己的形象创造了人类的祖先,男人亚当,再用亚当的一根肋骨创造了女人夏娃,并安置这对男女住在伊甸园中。而JVM中Eden区的命名就是借鉴了这个,Eden区用来放置刚刚创建的对象的地方

Survivor区

Survivor区:Survivor中文是幸存者的意思。
Eden区没有被回收的会进入Survivor区,也叫S区。S区的对象,分代年龄至少为1。
Survivor区分为两块S0和S1,按角色划分也可以叫做From和To。在同一时间,S0和S1只能有一个区有数据(保存存活对象),另外一个是空的(来完成内存之间的复制)也就是用S区另一半的空间的浪费(同一时间S0和S1只能有一个区有数据),来换回空间的连续性

当进行一次Young GC操作,S0也就是From区中对象的分代年龄就会+1,在Eden区中所有存活的对象会被复制到To 区,From区中还能存活的对象有两种:
①分代年龄达到年龄阈值对象:此时对象会被移动到Old区,
②分代年龄没有达到阈值的对象:会被复制到To 区。
此时Eden区和From区已经被清空(被GC的对象肯定没了,没有被GC的对象都有了各自的去处)。这时候From和To 交换角色,之前的From变成了To ,之前的To 变成了From。to区是进行空间保留的,也就是说无论如何都要保证名为To 的Survivor区域是空的。

【理论上,S1和S0的大小应该是一致的,因为要进行内存复制,但是在某些垃圾回收器上,它会动态的调整S0和S1的大小,可能会造成不一致的情况,这块具体的实现,需要看该垃圾回收器。这里关于S0和S1,以及From和To,不需要纠结叫法,因为两块区域都一样,按自己理解记就行】

Young GC会一直重复这样的过程,直到To区被填满,然后会将所有对象复制到老年代中。

Survivor区为什么分为S0和S1?为什么同一时间S0和S1只能有一个区有数据,另外一个是空的?

在Eden区不断的创建对象,当Eden区不够存放的时候,就会回收掉一部分,这个时候,Eden区的内存不连续了,会将未回收的对象通过内存复制的方式放入S区,而如果只有一个S区,下一次Young GC之后,可能S区的空间都不连续了,显然一个S区并不能够解决这个问题。
请添加图片描述
因此将Survivor区分为S0和S1。
采用这样的方式,Eden区区分配新的对象,其中一个S区保存存活对象,另一个S区去进行空间保留,永远要保证其中一个S区什么东西都不放,来完成内存之间的复制,也就是用S区另一半的空间的浪费(同一时间S0和S1只能有一个区有数据),来换回空间的连续性。
请添加图片描述

如何理解各种GC

hotsport的两类GC

无论GC如何命名,其实只是便于理解。真正在hotsport中,并没有那么多分类,它只有两类GC:

  • Partial GC(部分的GC): Partial就是部分的意思,Partial GC回收部分空间的模式,包括所谓的Young GC、Old GC,这种部分区域的GC都是属于Partial GC
  • Full GC(完整的GC): Full中文是完整的、满的意思,Full GC代表是全局的GC,所有区域的一个GC,包括Old区、Young区、以及Meta Space方法区

Young GC(Minor GC)

比如Young区有很多对象进入,塞满了,然后会Young区进行垃圾回收,对于Young区的GC操作,一般叫做Young GC,也叫做新生代GC,也叫做Minor GC,Minor中文意思是较小的。

Young区对象什么时候去Old区?

除了分代年龄到达阈值,去到Old区,还有另一种情况。

举个场景,在两次Young GC之间,产生了大量的对象A,且一直存活者。
假设一个对象2M,S0和S1大小10M,第一次Young GC之后,Eden区清空,S0使用2M,在到达第二次Young GC之前,产生了大量的对象,占满了Eden区,这个时候又要进行垃圾回收,回收之后,大量的A对象沾满了S0,但是由于分代年龄没有达到阈值,因此会导致在每次GC的时候,都会在S0和S1之间来回内存复制,占据大量的S区空间,同时S区被占满,因此复制的是相同的内容,这样也使S区失去了流动性。而且S区满了也会进行Young GC,这样会增加GC的频率。这样肯定是不允许的。

因此为了防止S区总是被占用,所以hotsport中在进入Old区的时候,还有一个判断是针对S区占用超过阈值的判断。

如jdk1.8中,S区中的其中一块内存当中,如果相同年龄的对象的大小的总和占比S区的大小,超过这个阈值,那么年龄 ≥ 这个年龄的对象会直接进入Old区 。所以很多时候在Old区发现分代年龄较小的对象,就是这个原因。

这样在某种程度上避免了在单次GC的间隙,所产生的垃圾过多,或者产生的对象过多所导致的问题。

但是又会有新的问题产生,去往Old区的判断有年龄 ≥ 这个年龄的选项,而放入对象势必会给Old区造成负担。但是相比于Old区的负担来说,总比S区失去了流动性强。这种策略在JVM其它地方也会涉及到。

担保机制

比如Eden区大小80M,S区大小10M,Old区大小200M,这个时候new了一个对象,大小为90M,就会直接放入Old区,这个就是担保机制。当新生代内存不够用的时候,我们Old区就会出来接收它,Old区大小默认为Young区的2倍。这个并不是Young区向Old区借内存,而是直接放入Old区。

在某些特定的垃圾回收器中,会有一个参数来控制对象大小,超过这个值,会直接放入Old区。

为什么默认Eden区和S区的比例是8:1:1?

可不可以设置成4:3:3?

可以的,这个值可以任意设置。通过设置SurvivorRatio参数:

-XX:SurvivorRatio=4

这样设置会有一些问题。

Eden区设置太小的问题:
新创建的对象都是放入Eden区的,放慢之后触发Young GC,而Eden区的比值由8变到4,表示其大小差了一倍,意味着Young GC的频率增加一倍,这个就需要注意了,因为GC不是一件好事情。因为GC意味着需要占用OS的线程的资源,去帮你进行标记和清除那些垃圾,也就是说GC回去抢占CPU的时间片,而如果这个时候业务正在使用,那么GC就会争夺到业务的资源,所以说Eden区太小,GC的次数就会越多。

Eden区设置太大的问题:
Eden区设置大,意味着S区的空间会变小。而S区满了也会去触发Young GC。

实际上,我们并不会等到i真的占满,这里面有个使用率一说,所以官方推荐的参数配置并不一定好,很多时候,需要开发者结合业务需求去自行配置合适的比例。

Old GC(Major GC)

Old区的GC叫做Old GC,只是针对Old这个部分区域进行回收,也叫做Major GC。

引发Full GC的方式

minor GC之后晋升的对象会进入老年代,老年代分为两种策略:
①悲观策略
悲观策略每次认为都可能达到预期的空间,悲观策略下有两种情况会触发Full GC:
:之前每次GC晋升对象的平均大小(这里指空间大小,而不是年龄大小,年龄在这里已经失去了意义,因为老年代已经不需要再去往上晋升),大于老年代的剩余空间。

  • 例如: 第一次minnor GC晋升的对象20M,第二次30M,第三次20M,这个时候平均是23.3M,假如现在Old区还剩21M空间,这个时候,从概率学上说,即使第四次晋升对象仍然是20M,该策略也会根据21M<23.3M,去进行一次Full GC。

:minnor GC之后,S区里面的存活对象大小超过了Old区的剩余空间,会触发Full GC。因为每次GC,晋升对象的大小是无法预测的,所以悲观的的情况下就是认为整个S区的对象都晋升,而Old区还有一个作用,就是为Young区进行担保,需要保证有足够的空间满足担保,如果不满足,它会抢先触发一次Full GC。

  • 例如: 假如现在Old区还剩8M空间,S区大小为10M,它会抢先触发一次Full GC。

②常规策略
常规策略有两种会触发Full GC,
:Meta Space空间不足,会直接引发Full GC,Full GC就代表会进行Young GC和Old GC。一般情况下,Meta Space GC必然是内存泄漏了。
:由System.gc()来触发Full GC(可以通过设置参数来禁止调用System.gc())。值得注意的是,System.gc()是一个通知的方式,并非立即执行,什么时候执行是不确定的,可能一年后执行,但是肯定会执行,不能说是不执行。

Meta Space是方法区的实现,它有个别称叫做“非堆”

垃圾收集器的GC

垃圾回收器中GC的实现各不相同。有些垃圾回收器还会有一些特殊的GC方式,这种特殊的GC会回收Young区和部分的Old区,这种模式只存在于也顶的垃圾收集器当中,后续垃圾收集器再具体说明。

面试时,谈到GC,也时要具体的讲到某种垃圾收集器而言,而不是就上面内容讲一下,因为在具体的垃圾收集器上,所谓的一个回收策略,会有很大的不一样,会有特殊的策略。

比如垃圾收集器CMS就会在回收Old区的时候,顺带触发一次Young GC。

对象创建到GC的过程

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区。

Eden区==>Survivor区:普通的新创建的Java对象放在Eden区,在Eden区还有很多分代年龄为0的对象,他们有的大,有的小,随着Eden区中的对象变得越来越多,到了无法容忍的地步,触发了Young GC,干掉了Eden区中的大量对象,剩余的幸存者分代年龄+1之后,发配到Survivor区。

Survivor==>Old区:随着每次GC的发生,幸存者们的分代年龄会对应的+1,到达指定年龄之后,Survivor区会将这些幸存者分配到年老代那边,那里空间很大,幸存者很多,并且年龄都挺大的,当然,也可以看到一些年龄较小,但是占位很大的幸存者。

Survivor区S0和S1空间复制:Survivor区分为两个同样大小的区域S0和S1,同一时刻只有一个区域用来安置幸存者(类似灾难片的居住区),另一个区域什么都不放(类似灾难片的备用区)。随着每次GC的发生,Survivor区会迎来新的幸存者,也会有原来的幸存者被干掉,还有的则是年纪太大去了Old区。由于幸存者对象大小并不一致,Survivor区腾出的床位(空间)并不完美契合新的幸存者,会造成空间浪费,因此需要重新分配合适大小的床位(空间)。所以将这些对象通通分配备到Survivor区空闲的那个区域,在那里给他们统一重新分配各自适合的床位,来节省空间。From和To的角色也是这样来的,To表示空闲的那个区域,From表示现有对象的区域。
请添加图片描述

思考

JVM内存模型理论上了解了,如何查看JVM视图呢?

汇总

JVM1:官网了解JVM;Java源文件运行过程、javac编译Java源文件、如何阅读.class文件、class文件结构格式说明、 javap反编译字节码文件;类加载机制、class文件加载方式

JVM2:类加载机制、class文件加载方式;类加载的过程:装载、链接、初始化、使用、卸载;类加载器、为什么类加载器要分层?JVM类加载机制的三种方式:全盘负责、父类委托、缓存机制;自定义类加载器

JVM3:图解类装载与运行时数据区,方法区,堆,运行时常量池,常量池分哪些?String s1 = new String创建了几个对象?初识栈帧,栈的特点,Java虚拟机栈,本地方法发栈,对象指向问题

JVM4:Java对象内存布局:对象头、实例数据、对齐填充;JOL查看Java对象信息;小端存储和大端存储,hashcode为什么用大端存储;句柄池访问对象、直接指针访问对象、指针压缩、对齐填充及排序

JVM5:JVM内存模型与运行时数据区的关系,堆为什么分区,分代年龄,Young区划分,Survivor区为什么分为S0和S1,如何理解各种GC:Partial GC、Full GC、Young GC

JVM6:JVM内存模型验证;使用visualvm查看JVM视图;Visual GC插件下载链接;模拟JVM常见错误,模拟堆内存溢出,模拟栈溢出,模拟方法区溢出

JVM7:垃圾回收是什么?从运行时数据区看垃圾回收到底回收哪块区域?垃圾回收如何去回收?垃圾回收策略,引用计数算法及循环引用问题,可达性分析算法

JVM8:引用是什么?强引用,软引用,弱引用,虚引用,ReferenceQueue引用队列;对象生命周期有哪些阶段?创建、应用、不可见、不可达、收集、终结、对象空间重分配;重写finazlie方法弊端

JVM9:STW:stop the world,什么时候会垃圾回收?垃圾收集算法:标记清除算法、标记复制算法、标记整理算法;清除算法的整理顺序:任意顺序,滑动顺序;什么是分代收集算法?

JVM10:JVM参数分类,JVM标准参数,JVM非标准参数,-X参数,-XX参数,其他参数;查看JVM参数,idea控制台输出JVM参数,单位换算;设置JVM参数的常见方式;常用JVM参数及含义

JVM11:垃圾收集器的并发和并行,Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old,源码分析CMS两种模式,CMS如何定位可达对象?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值