目录
一 什么是GC 分代
二 GC 为什么需要分代
三 GC 如何分代,每一个代具体是怎么工作的
3.1 年轻代
3.1.1 Eden Space
3.1.2 Survivor
3.2 老年代
3.3 持久代
3.3 年轻代和老年代的工作方式
四 元数据空间的参数
我们知道GC为了方便垃圾回收,根据对象的特点对内存做了内存分代,在JDK1.8 之前主要包括新生代,老年代和永久代,在JDK1.8之后主要是新生代,老年代。方法区的实现元数据区直接分配在直接内存上的,不受JVM 管理。如图示
第一:如果不分代,那么内存只有快满的时候才会进行垃圾回收,因为需要收集垃圾对象太多,所以耗费时间,会造成长时间的应用程序卡顿
第二:对象的生命周期不同,生命周期比较短的对象,如果及时回收,可以提升内存利用率
所以,分代方便垃圾回收,也可以提升内存利用率
GC总的来说,堆没有什么太大的变化,对一般就分为年轻到和老年代。
年轻代分为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
如果新对象不是大对象,一般新对象就直接分配在Eden区;如果对象很大,就直接分配在老年代了。
Survivor是存活区,顾名思义,就是存放存活对象(Live Object)的地方。他被分为from(S0)和to(S1)两部分,主要用于年轻代回收是存活对象的拷贝。
老年代是大对象分配时,直接分配在老年代;另外就是当存活区的对象的age超过指定的大小,就会进入存活区;或者新对象在年轻代回收后Eden和Survivor区都没有空间,则只能分配到老年代
我们可以设置对象的age值,-XX:MaxTenuringThreshold=n
当然我们也可以设置年轻代占老年代的内存比重,通过设置参数:
-XX:NewRatio=N,这个值默认是2,表示新生代内存栈老年代内存比重为1:2,即表示年轻代占用堆内存的1/3;老年代占用堆内存的2/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进行回收。
#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。