JVM专题之分代模型:年轻代、老年代、永久代

目录

 

一 什么是GC 分代

二 GC 为什么需要分代

三 GC 如何分代,每一个代具体是怎么工作的

3.1 年轻代

3.1.1 Eden Space

3.1.2 Survivor

3.2 老年代

3.3 持久代

3.3 年轻代和老年代的工作方式

四 元数据空间的参数


一 什么是GC 分代

我们知道GC为了方便垃圾回收,根据对象的特点对内存做了内存分代,在JDK1.8 之前主要包括新生代,老年代和永久代,在JDK1.8之后主要是新生代,老年代。方法区的实现元数据区直接分配在直接内存上的,不受JVM 管理。如图示

 

二 GC 为什么需要分代

第一:如果不分代,那么内存只有快满的时候才会进行垃圾回收,因为需要收集垃圾对象太多,所以耗费时间,会造成长时间的应用程序卡顿

第二:对象的生命周期不同,生命周期比较短的对象,如果及时回收,可以提升内存利用率

所以,分代方便垃圾回收,也可以提升内存利用率

三 GC 如何分代,每一个代具体是怎么工作的

GC总的来说,堆没有什么太大的变化,对一般就分为年轻到和老年代。

3.1 年轻代

年轻代分为Eden区,Survivor0区和Survivor1区。他们之间的默认比例: Eden:S0:S1 = 8:1:1。我们可以通过参数设置Eden和Survivor的内存空间容量比。

-XX:SurvivorRatio=N 表示Eden占用年轻代内存比例为 N/(N+2),S0 占比 = 1 / (N+2),S1 占比 = 1 / (N+2)。假设-XX:SurvivorRatio=5,则Eden占比为5/7,S0和S1各占比1/7

 

当然我们也可以设置年轻代占老年代的内存比重,通过设置参数:

-XX:NewRatio=N,这个值默认是2,表示新生代内存栈老年代内存比重为1:2,即表示年轻代占用堆内存的1/3;老年代占用堆内存的2/3。

 

假设堆内存2G,-XX:NewRatio=4 -XX:SurvivorRatio=6,则计算年轻代和老年代各占多少内存,年轻代中Eden和S0和S1各占多少?

新生代内存: 1/(1+4) * 2G = 0.4G;

老年代: 4/(1+4) * 2G= 1.6G;

Eden = 6/(6+2) * 0.4G = 0.3G

S0 = 1/(6+2) * 0.4G = 0.05G

S1 = 1/(6+2) * 0.4G = 0.05G

 

3.1.1 Eden Space

如果新对象不是大对象,一般新对象就直接分配在Eden区;如果对象很大,就直接分配在老年代了。

 

3.1.2 Survivor

Survivor是存活区,顾名思义,就是存放存活对象(Live Object)的地方。他被分为from(S0)和to(S1)两部分,主要用于年轻代回收是存活对象的拷贝。

 

3.2 老年代

老年代是大对象分配时,直接分配在老年代;另外就是当存活区的对象的age超过指定的大小,就会进入存活区;或者新对象在年轻代回收后Eden和Survivor区都没有空间,则只能分配到老年代

 

我们可以设置对象的age值,-XX:MaxTenuringThreshold=n

 

当然我们也可以设置年轻代占老年代的内存比重,通过设置参数:

-XX:NewRatio=N,这个值默认是2,表示新生代内存栈老年代内存比重为1:2,即表示年轻代占用堆内存的1/3;老年代占用堆内存的2/3。

 

 

3.3 永久代

持久代跟堆没啥关系,因为他是方法区的实现。主要存放一些类的元数据,比如类、字段、方法等元数据等等。当持久代满了的时候,也会抛出内存溢出异常,可以通过-XX:PermSize及-XX:MaxPermSize调整。

随着动态类加载到越来越多,这块内存 变得不太可控,设置太小,容易出现内存溢出,设置太大,浪费内存空间。所以Metaspace出现了,它出现的目的就是让方法区内存管理不再受限制。因为默认参数MaxMetaspaceSize就是很大,理论上只要内存最够就不会内存溢出。

 

JDK 1.8 之后,移除了持久代,用元数据空间代替了持久代,成为方法区的实现。其中静态变量和字符串常量池移到堆中;符号引号移到了直接内存,这些都不在元数据空间中明。元数据空间因为是分配在直接内存的,当Metaspace使用达到Full GC阀值,就会进行Full GC,那这个阀值是多少呢?

#1 对于元数据空间,其初始大小并不等于设置的-XX:MetaspaceSize参数,随着类的增加,Metaspace会不断扩容,直到达到 -XX:MetaspaceSize 触发 GC。我们无法设置这个参数初始大小的。

#2 -XX:MetaspaceSize和-XX:MaxMetaspaceSize主要是用来确定Metaspace进行Full GC的阀值。其中-XX:MetaspaceSize默认是21807104(约20.8M),可以通过jinfo -flag MetaspaceSize pid查看这个值。-XX:MaxMetaspaceSize主要是用来确定元数据扩容最大阀值,默认是很大,也就是说只要内存足够,就不会发生内存溢出。

这两个参数,我们一般不建议使用默认的,我们建议这个两个参数值一致,都设置为256M,大多数应用程序基本满足:

-XX:MetaspaceSize=256m

-XX:MaxMetaspaceSize=256m

#3 如果老年代使用CMS垃圾收集器,则在Metaspace进行Full GC时候使用CMS进行回收。

 

 

3.3 年轻代和老年代的工作方式

#1 JVM启动,新分配对象到Eden,这时候Survivor区和老年代都还没有对象。

#2 当Eden空间不足的时候,这时候会进行一次Minor GC,即从GC Root开始查找直接对象,然后向下递归标记存活对象,然后将存活对象拷贝到S0(from)区,并且S0区的存活对象age值加1,然后把Eden区清空。(注意,取决于年轻代使用何种垃圾回收器,从而决定垃圾回收是不是多线程执行)

#3当Eden空间不足的时候,这时候又会进行一次Minor GC,这时候会同时检测Eden 和 S0,看有哪些对象存活,然后将这些对象拷贝到S1,并且对象的age值递增,清空Eden和S0区,S0区作为备用区

#4当Eden空间不足的时候,这时候又会进行一次Minor GC,这时候会同时检测Eden 和 S1,看有哪些对象存活,然后将这些对象拷贝到S0,并且对象的age值递增,清空Eden和S1区,S1又称为备用区

#5伴随着age的增长,年龄值达到阀值,默认为15,就会把这些对象移动从Survivor区移到到老年代。

 

#6 随着时间的推进,越来越多的age值超过阀值的对象从Survivor区移到了老年代或者大对象分配到了老年代,老年代的空间逐渐被占满了,这时候就会进行一次老年代的垃圾回收。取决于垃圾回收器,如果是Serial Old就是暂停整个应用程序进行单线程回收;Parallel Old就是暂停整个应用程序进行多线程回收;CMS相对于前面的来说,可以降低停顿时间。

#7 假设新分配对象在年轻代进行minor gc之后依然没有内存可供分配,而且老年代也达到阀值,无法分配则会进行Full GC;或者老年代内存不够,无法分配,则也会进行Full GC。一般Full GC代价是很大,它会暂停所有应用程序,对整个堆进行内存回收。

 

四 元数据空间的参数

#1 对于元数据空间,随着元数据(类信息、方法信息、普通常量)等加载的越来越多,一定有可能触发GC, 当元数据空间触发GC的时候,就是触发Full GC, 它会暂停所有应用程序,对整个堆进行内存回收。

 

默认,如果我们没有设置-XX:MetaspaceSize参数,那么当元数据空间大小达到20.78M就会触发Full GC, 如果20.78M就触发Full GC,未免太频繁了,所以一般会设置-XX:MetaspaceSize大小,突破20.78M的限制, 比如256M或者512M。如果-XX:MetaspaceSize没有限制,可以设置成无限大,直到耗尽所有的内存。所以为了避免出现这种情况,一般会设置触发Full GC的阈值上限,即通过-XX:MaxMetaspaceSize参数来设置触发Full GC的阈值上限。通常会将-XX:MetaspaceSize和-XX:MaxMetaspaceSize设置成相同的值,对于8G的内存一般设置成256M就差不多了。
 

 

#2 -XX:MetaspaceSize和-XX:MaxMetaspaceSize主要是用来确定Metaspace进行Full GC的阀值。其中-XX:MetaspaceSize默认是21807104(约20.8M),可以通过jinfo -flag MetaspaceSize pid查看这个值。-XX:MaxMetaspaceSize主要是用来确定元数据扩容最大阀值,默认是很大,也就是说只要内存足够,就不会发生内存溢出。

这两个参数,我们一般不建议使用默认的,8G内存我们建议这个两个参数值一致,都设置为256M,大多数应用程序基本满足:

-XX:MetaspaceSize=256m

-XX:MaxMetaspaceSize=256m

 

#3 -XX:MinMetaspaceFreeRatio: 默认40

每次GC完成,可以被提交的内存 / (元数据空间当前使用内存+即将被提交的内存) < 40%, 表示现有剩余的内存不足以分配提交的内存,这时候需要

扩大MetaspaceSize大小。

假设MetaspaceSize=20M,GC后Metaspace正在使用的是12M,还有8M可以被使用,如果新提交内存10M,

(20 - 12) / (12 + 10) = 36 % < 40 %. 所以需要扩容。

 

那到底扩多少,有2个参数控制:

-XX:MinMetaspaceExpansion 表示每次最少扩多少,默认是332K

 

如果(要提交的内存-MetaspaceSize) < MinMetaspaceExpansion ,那我们将MetaspaceSize扩容到(MetaspaceSize+MinMetaspaceExpansion)

如果(要提交的内存-MetaspaceSize) > MinMetaspaceExpansion , 我们怎么办呢?

 

这里还有一个参数:

-XX:MaxMetaspaceExpansion 表示每次最多扩多少,默认是5.2M. (要提交的内存-MetaspaceSize) > MinMetaspaceExpansion但是小于

MaxMetaspaceExpansion,我们就将MetaspaceSize扩容到(MetaspaceSize+MaxMetaspaceExpansion)

 

#4 -XX:MaxMetaspaceFreeRatio:

每次GC之后,有可能元数据空间中此次回收对象比较多,从而剩余的可提交空闲内存多,为了避免内存浪费,我们设置一个最大空闲比率,默认70%,只要空闲比率超过这个值,则回收部分MetaspaceSize阀值。

假设-XX:MetaspaceSize=20M, 现在此次回收之后,Metaspace只有5,那么剩余有效空闲空间20-5 = 15, 那么此时剩余空闲空间占比为,15 / 20  = 75%,这个值大于70%所以Metaspace需要回收MetaspaceSize。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值