JVM垃圾回收机制


前言,大家好,我是程序员八哥,一个三年经验的小厂程序员,如果你正在面试,希望你一定要把这篇文章看完。如果不是,那就没必要看下去,作用不大,如果你非要看,向你致敬,solo。

堆内存详解

![image.png](https://img-blog.csdnimg.cn/img_convert/573015023adaffaad2b39c97b88594cc.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=227&id=u8224b233&margin=[object Object]&name=image.png&originHeight=384&originWidth=732&originalType=binary&ratio=1&rotation=0&showTitle=false&size=96779&status=done&style=none&taskId=u81ee75fe-9833-4455-953e-1085b88c2b8&title=&width=432)
堆内的结构,默认配置:年轻代分为eden区,from区 to区,比例:8:1:1 ;新生代:老年代=1:2
为什么年轻代的比例设置是这样的,事实上,现在商用的虚拟机都采用这种算法来回收新生代。因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。正如在博文《JVM 内存模型概述》中介绍的那样,实践中会将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间 (如下图所示),每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用内存空间为整个新生代容量的90% ( 80%+10% ),只有10% 的内存会被“浪费”。

JVM 内存分配策略

1.对象优先在 Eden 区

多数情况下,新对象都在新生代 Eden 区分配,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC,即发生在新生代的垃圾收集。如果分配后还是没有足够的空间,就会启动分配担保机制在老年代分配空间。

2.⼤对象直接进⼊⽼年代

⼤对象是指需要连续内存空间的对象,⽐如很⻓的字符串以及数组。⽼年代直接分配的⽬的是避免在 Eden 区和 Survivor 区之间出现⼤量内存复制。经常出现大对象,会导致在内存还有不少时就触发垃圾回收。
可以通过虚拟机参数 -XX:PretenureSizeThreshold 控制大对象的最小临界值。

⻓期存活的对象进⼊⽼年代

虚拟机给每个对象定义了年龄计数器,对象在 Eden 区出⽣之后,如果经过⼀次 Minor GC 之后,将进⼊ Survivor
区,同时对象年龄变为 1,增加到⼀定阈值时则进⼊⽼年代(阈值默认为 15)
可以通过参数 -XX:MaxTenuringThreshold 设置进入老年代的年龄上限。

动态对象年龄判定

为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到阈值才能进⼊⽼年代。如果在 Survivor 区中相同年龄的所有对象的空间总和⼤于 Survivor 区空间的⼀半,则年龄⼤于或等于该年龄的对象直接进⼊ ⽼年代。

空间分配担保

在发⽣ Minor GC 之前,虚拟机会先检查⽼年代最⼤可⽤的连续空间是否⼤于新⽣代所有对象的空间总和,如果这个条件成⽴,那么 Minor GC 可以确保是安全的。如果不成⽴则进⾏ Full GC。

对象可回收判定

1.引用计数法

给对象添加⼀个引⽤计数器,每当由⼀个地⽅引⽤它时,计数器值就加1;当引⽤失效时,计数器值就减1;任何时 刻计数器为0的对象就是不可能再被使⽤的。

  • 优点:实现简单,判定效率也很⾼
  • 缺点:他很难解决对象之间相互循环引⽤的问题,基本上被抛弃

![image.png](https://img-blog.csdnimg.cn/img_convert/2f7c0beb388bcd2a6c875241b6205349.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=332&id=ud6e4cd38&margin=[object Object]&name=image.png&originHeight=332&originWidth=732&originalType=binary&ratio=1&rotation=0&showTitle=false&size=168723&status=done&style=none&taskId=u979df3f4-85d1-4b98-9537-3b5749f4202&title=&width=732)

2.可达性分析法:

通过⼀系列的成为“GC Roots”(活动线程相关的各种引⽤,虚拟机栈帧引⽤,静态变量引⽤,JNI引⽤)的对象作为起始点,从这些节点ReferenceChains开始向下搜索,搜索所⾛过的路径成为引⽤链,当⼀个对象到GC ROOTS没有任何引⽤链相连时,则证明此对象时不可⽤的;

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中Native方法引用的对象;

优点:可以解决循环引用的问题
![image.png](https://img-blog.csdnimg.cn/img_convert/1ce1a46aa48b6b4d280cdad4212d555a.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=349&id=uca80a821&margin=[object Object]&name=image.png&originHeight=349&originWidth=690&originalType=binary&ratio=1&rotation=0&showTitle=false&size=148347&status=done&style=none&taskId=u23303d29-04b2-41b9-b336-3e7ce43662c&title=&width=690)

3.两次标记过程:

对象被回收之前,该对象的finalize()⽅法会被调⽤;两次标记,即第⼀次标记不在“关系⽹”中的对象。第⼆次的话就 要先判断该对象有没有实现finalize()⽅法了,如果没有实现就直接判断该对象可回收;如果实现了就会先放在⼀个队列中,并由虚拟机建⽴的⼀个低优先级的线程去执⾏它,随后就会进⾏第⼆次的⼩规模标记,在这次被标记的对象就会真正的被回收了。

垃圾回收算法

**垃圾回收算法:复制算法、标记清除、标记整理、分代收集 **

复制算法:(young)

将内存分为⼤⼩相同的两块,每次使⽤其中的⼀块。当这⼀块的内存使⽤完后,就将还存活的对象复制到另⼀块去,然后再把使⽤的空间⼀次清理掉。这样就使每次的内存回收都是对内存区间的⼀半进⾏回收;

  • 优点:实现简单,内存效率⾼,不易产⽣碎⽚ ,适用于对象存活率低的场景,比如年轻代
  • 缺点:内存压缩了⼀半,倘若存活对象多,Copying 算法的效率会⼤⼤降低

标记-清除算法:(cms)

标记-清除算法分为两个阶段,第一个阶段是使用可达性分析法标记出所有存活的对象,标记完毕后,再扫描整个空间把没有被标记的对象清除。
缺点:标记和清除效率低,标记清除后会产⽣⼤量不连续的碎⽚,需要预留空间给分配阶段的浮动垃圾
![image.png](https://img-blog.csdnimg.cn/img_convert/0707bff3b123df0f04db23f3c0ce6151.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=459&id=u5f356c07&margin=[object Object]&name=image.png&originHeight=459&originWidth=558&originalType=binary&ratio=1&rotation=0&showTitle=false&size=87673&status=done&style=none&taskId=u5f78c60c-9c5b-415d-81f6-9fc2371dfe3&title=&width=558)

标记整理算法:(old)

标记过程仍然与“标记-清除”算法⼀样,但扫描时的后续步骤不一样,而是让所有存活的对象向⼀端移动,然后直接清理没有被标记的对象,类似于磁盘整理的过程;解决了产⽣⼤量不连续碎⽚问题
优点:适合于对象存活率高的场景,比如说老年代
![image.png](https://img-blog.csdnimg.cn/img_convert/b7b7f3c9967d432d3b285d76df916854.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=583&id=u3eb2ccf7&margin=[object Object]&name=image.png&originHeight=583&originWidth=610&originalType=binary&ratio=1&rotation=0&showTitle=false&size=103441&status=done&style=none&taskId=u0fba2921-06e9-4f9a-ab57-ad76a25e6c4&title=&width=610)
作用如下:
![image.png](https://img-blog.csdnimg.cn/img_convert/01deae43407d9cd97d8534906b4c2d09.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=105&id=u9c2421a2&margin=[object Object]&name=image.png&originHeight=105&originWidth=585&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35110&status=done&style=none&taskId=uf852ecd9-850e-466a-9058-86e13fb666b&title=&width=585)

分代收集算法:

在一个大型系统,创建的对象的生命周期是各种各样的,我们最好的做法是将对象做好分类,然后根据每个分类的特性使用适合的垃圾回收算法进行针对性处理,以此来提高垃圾清除的效率。因此而诞生了分代收集算法,根据各个年代的特点选择合适的垃圾收集算法。
新⽣代采⽤复制算法,新⽣代每次垃圾回收都要回收⼤部分对象,存活对象较少,即要复制的操作⽐较少,⼀般将新⽣代划分为⼀块较⼤的 Eden 空间和两个较⼩的 Survivor 空间(From Space, To Space),每次使⽤Eden空间和其中的⼀块 Survivor 空间,当进⾏回收时,将该两块空间中还存活的对象复制到另⼀块 Survivor 空间中。
⽼年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进⾏垃圾收集。
Java堆内存一般可以分为新生代、老年代和永久代三个模块,如下图所示:

新生代(Young Generation)

新生代的目标就是尽可能快速的收集掉那些生命周期短的对象,一般情况下,所有新生成的对象首先都是放在新生代的。新生代内存按照 8:1:1 的比例分为一个eden区和两个survivor(survivor0,survivor1)区,大部分对象在Eden区中生成。
在进行垃圾回收时,先将eden区存活对象复制到survivor0区,然后清空eden区,当这个survivor0区也满了时,则将eden区和survivor0区存活对象复制到survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后交换survivor0区和survivor1区的角色(即下次垃圾回收时会扫描Eden区和survivor1区),即保持survivor0区为空,如此往复。特别地,当survivor1区也不足以存放eden区和survivor0区的存活对象时,就将存活对象直接存放到老年代。
如果老年代也满了,就会触发一次FullGC,也就是新生代、老年代都进行回收。注意,新生代发生的GC也叫做MinorGC,MinorGC发生频率比较高,不一定等 Eden区满了才触发。

老年代(Old Generation)

老年代存放的都是一些生命周期较长的对象,就像上面所叙述的那样,在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。
此外,老年代的内存也比新生代大很多(大概比例是1:2),当老年代满时会触发Major GC(Full GC),老年代对象存活时间比较长,因此FullGC发生的频率比较低。

永久代(Permanent Generation)

永久代主要用于存放静态文件,如Java类、方法等。
永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如使用反射、动态代理、CGLib等bytecode框架时,在这种时候需要设置一个比较大的永久代空间来存放这些运行过程中新增的类。
小结
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。垃圾回收有两种类型,Minor GC 和 Full GC。

  • GC:对新生代进行回收,不会影响到年老代。因为新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。
  • Full GC:也叫 Major GC,对整个堆进行回收,包括新生代、老年代和永久代。由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数,导致Full GC的原因包括:老年代被写满、永久代(Perm)被写满和System.gc()被显式调用等。

![image.png](https://img-blog.csdnimg.cn/img_convert/e25def96d0c57573df61d5affa491c78.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=125&id=ue991f8cc&margin=[object Object]&name=image.png&originHeight=308&originWidth=563&originalType=binary&ratio=1&rotation=0&showTitle=false&size=103884&status=done&style=none&taskId=u61c0ab64-3758-4024-b5c1-897446da2b8&title=&width=229)

垃圾回收器

Serial(复制算法):

Serial 是⼀个单线程的收集器,在标记和清除的过程中都使用单线程。它不但只会使⽤⼀个 CPU 或⼀条线程去完成垃圾收集⼯作,并且在进⾏垃圾收集的同时,必须暂停其他所有的⼯作线程,直到垃圾收集结束。适合⽤于客户端垃圾收集器。
![image.png](https://img-blog.csdnimg.cn/img_convert/ab41e1c0ad5934079cc37972cf808067.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=281&id=u2db1ebc5&margin=[object Object]&name=image.png&originHeight=281&originWidth=722&originalType=binary&ratio=1&rotation=0&showTitle=false&size=92229&status=done&style=none&taskId=u40af60cb-5ace-4f23-bd86-8a907238621&title=&width=722)

Parnew(复制算法):

ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使⽤复制算法,除了使⽤多线程进⾏垃圾收集之外,其余的⾏为和 Serial 收集器完全⼀样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的⼯作线程。
![image.png](https://img-blog.csdnimg.cn/img_convert/11b8855a22c25f8a71ab1be933100bcf.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=263&id=u5c95caa2&margin=[object Object]&name=image.png&originHeight=263&originWidth=716&originalType=binary&ratio=1&rotation=0&showTitle=false&size=97324&status=done&style=none&taskId=ufc1b04fb-24d0-48b7-9567-53cb675fc70&title=&width=716)

parallel Scavenge:(关注吞吐量)

Parallel Scavenge收集器关注点是吞吐量(⾼效率的利⽤CPU)。CMS等垃圾收集器的关注点更多的是⽤户线程的停顿时间(提⾼⽤户体验);⾼吞吐量可以最⾼效率地利⽤ CPU 时间,尽快地完成程序的运算任务,主要适⽤于在后台运算⽽不需要太多交互的任务。

Serial old(标记-整理):

Serial收集器的⽼年代版本,它同样是⼀个单线程收集器,使⽤标记-整理算法。主要有两个⽤途:
在 JDK1.5 之前版本中与新⽣代的 Parallel Scavenge 收集器搭配使⽤。
作为年⽼代中使⽤ CMS 收集器的后备垃圾收集⽅案。
![image.png](https://img-blog.csdnimg.cn/img_convert/ba99f7f53cd7897f516c0ebe144f0bfa.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=271&id=u723a2ac2&margin=[object Object]&name=image.png&originHeight=271&originWidth=734&originalType=binary&ratio=1&rotation=0&showTitle=false&size=86931&status=done&style=none&taskId=ue3856bd3-2813-4750-ba10-c0e5e6186ad&title=&width=734)

parallel old(标记-整理):

Parallel Scavenge收集器的⽼年代版本。使⽤多线程和“标记-整理”算法。
![image.png](https://img-blog.csdnimg.cn/img_convert/791be37e297c952806a5bc7d4152af8c.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=270&id=ubd3b2f3d&margin=[object Object]&name=image.png&originHeight=270&originWidth=740&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102658&status=done&style=none&taskId=u5799cc19-6ad9-4ec0-be21-334ab5c57ca&title=&width=740)

CMS 收集器

CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。
分为以下四个流程:

  • 初始标记: 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
  • 并发标记: 进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
  • 重新标记: 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
  • 并发清除: 不需要停顿。

在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
具有以下缺点:

  • 吞吐量低: 低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
  • 无法处理浮动垃圾。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure(晋升担保失败),这时虚拟机将临时启用 Serial Old 来替代 CMS。
  • 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。

![image.png](https://img-blog.csdnimg.cn/img_convert/2e4f3921d6909320181f1abbd02bb49f.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=264&id=u7cb41646&margin=[object Object]&name=image.png&originHeight=264&originWidth=733&originalType=binary&ratio=1&rotation=0&showTitle=false&size=135328&status=done&style=none&taskId=ue5840d76-eb24-4ba5-b441-1a1e1a631da&title=&width=733)

G1回收器

G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。
![image.png](https://img-blog.csdnimg.cn/img_convert/5d4a284e70d767abb725d53bb458bf9a.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=247&id=u3a6534a3&margin=[object Object]&name=image.png&originHeight=413&originWidth=735&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36772&status=done&style=none&taskId=u764f06ab-6261-492b-94f1-eecd7a87242&title=&width=440)
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
![image.png](https://img-blog.csdnimg.cn/img_convert/c445d21561cb33be55cbee4faa6dc740.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=257&id=ufc6a3579&margin=[object Object]&name=image.png&originHeight=511&originWidth=713&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35073&status=done&style=none&taskId=ub740fcbd-1aa1-484c-805f-ba0c9fe812d&title=&width=358)
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
每个 Region 都有一个 Remembered Set(集合),用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。通过维护一个数据冗余的缓存来大幅提高查询效率,这样的思想在设计系统也很常用。
![image.png](https://img-blog.csdnimg.cn/img_convert/8b531821c73034f5cf78612097ed2eb3.png#clientId=u74d63271-b745-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=178&id=u8b848cc6&margin=[object Object]&name=image.png&originHeight=249&originWidth=763&originalType=binary&ratio=1&rotation=0&showTitle=false&size=132567&status=done&style=none&taskId=ubfdb7451-6693-480d-9868-b080aa12e51&title=&width=546)
如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记: 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
  • 筛选回收: 首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

具备如下特点:

  • 空间整合: 整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
  • 可预测的停顿: 能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。

总结:核心思想是通过堆内存的区域划分来实现区域内的标记清除算法,区域外的标记整理算法,再加上CMS的并发收集思想,再通过Remembered Set 缓存技术等技巧。极大的提高了垃圾回收过程中的灵活性,可以根据用户线程的cpu使用率,动态的调整垃圾回收的速率,从而做到最大的吞吐量。

ZGC

ZGC与CMS中的ParNew和G1类似,也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。
ZGC垃圾回收周期如下图所示:
![](https://img-blog.csdnimg.cn/img_convert/7c940f06a00672f9d8e41c281b6c0e22.png#clientId=uccdef6fe-d9da-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=315&id=ub9464e31&margin=[object Object]&originHeight=940&originWidth=1812&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u5ee7c6b9-85b9-4580-9b9b-354f97bf7a6&title=&width=608)
ZGC只有三个STW阶段:初始标记再标记初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。而ZGC是增加了并发转移的过程,从而提高了垃圾回收的效率。

Full GC 的触发条件

对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:

1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2. 老年代空间不足

老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

3. 空间分配担保失败

使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第五小节。

4. JDK 1.7 及以前的永久代空间不足

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。
为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

5. Concurrent Mode Failure

执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。

附录

参数描述
-Xms堆内存初始大小,单位m、g
-Xmx堆内存最大允许大小,一般不要大于物理内存的80%
-XX:PermSize非堆内存初始大小,一般应用设置初始化200m,最大1024m就够了
-XX:MaxPermSize非堆内存最大允许大小
-XX:NewSize(-Xns)年轻代内存初始大小
-XX:MaxNewSize(-Xmn)年轻代内存最大允许大小
-XX:SurvivorRatio=8年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1
-Xss堆栈内存大小
-XX:NewRatio=老年代/新生代设置老年代和新生代的大小比例
-XX:+PrintGCjvm启动后,只要遇到GC就会打印日志
-XX:+PrintGCDetails查看GC详细信息,包括各个区的情况
-XX:MaxDirectMemorySize在NIO中可以直接访问直接内存,这个就是设置它的大小,不设置默认就是最大堆空间的值-Xmx
-XX:+DisableExplicitGC关闭System.gc()
-XX:MaxTenuringThreshold垃圾可以进入老年代的年龄
-Xnoclassgc禁用垃圾回收
-XX:TLABWasteTargetPercentTLAB占eden区的百分比,默认是1%
-XX:+CollectGen0FirstFullGC时是否先YGC,默认false

如果有兴趣的话,可以关注我的微信公众号“程序员八哥”,一起学习,一起进步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值