jvm之年轻代(新生代)老年代永久代以及GC原理详解GC优化
Posted chenhg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm之年轻代(新生代)老年代永久代以及GC原理详解GC优化相关的知识,希望对你有一定的参考价值。
关于JVM,也许你听过这些术语:年轻代(新生代)、老年代、永久代、minor gc(young gc)、major gc、full gc
不要急,先上图,这是jvm 堆内存结构图
仔细的你发现了 图中有些分数8/10和1/10,这是默认配置下各个代内存分配比例。
举个栗子:
假如总heap max分配1200M,那么年轻代占用1/3就是400M,老年代占2/3就是800M。
Eden占年轻代的8/10就是320M。Survivor占年轻代的2/10就是80M,from和to各占40M。
年轻代
也叫新生代,顾名思义,主要是用来存放新生的对象。新生代又细分为 Eden区、SurvivorFrom区、SurvivorTo区。
新创建的对象都会被分配到Eden区(如果该对象占用内存非常大,则直接分配到老年代区), 当Eden区内存不够的时候就会触发MinorGC(Survivor满不会引发MinorGC,而是将对象移动到老年代中),
在Minor GC开始的时候,对象只会存在于Eden区和Survivor from区,Survivor to区是空的。
Minor GC操作后,Eden区如果仍然存活(判断的标准是被引用了,通过GC root进行可达性判断)的对象,将会被移到Survivor To区。而From区中,对象在Survivor区中每熬过一次Minor GC,年龄就会+1岁,当年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置,默认是15)的对象会被移动到年老代中,否则对象会被复制到“To”区。经过这次GC后,Eden区和From区已经被清空。
“From”区和“To”区互换角色,原Survivor To成为下一次GC时的Survivor From区, 总之,GC后,都会保证Survivor To区是空的。
奇怪为什么有 From和To,2块区域?
这就要说到新生代Minor GC的算法了:复制算法
把内存区域分为两块,每次使用一块,GC的时候把一块中的内容移动到另一块中,原始内存中的对象就可以被回收了,
优点是避免内存碎片。
老年代
随着Minor GC的持续进行,老年代中对象也会持续增长,导致老年代的空间也会不够用,最终会执行Major GC(MajorGC 的速度比 Minor GC 慢很多很多,据说10倍左右)。Major GC使用的算法是:标记清除(回收)算法或者标记压缩算法。
标记清除(回收):1. 首先会从GC root进行遍历,把可达对象(存过的对象)打标记
2. 再从GC root二次遍历,将没有被打上标记的对象清除掉。
优点:老年代对象一般是比较稳定的,相比复制算法,不需要复制大量对象。之所以将所有对象扫描2次,看似比较消耗时间,其实不然,是节省了时间。举个栗子,数组 1,2,3,4,5,6。删除2,3,4,如果每次删除一个数字,那么5,6要移动3次,如果删除1次,那么5,6只需移动1次。
缺点:这种方式需要中断其他线程(STW),相比复制算法,可能产生内存碎片。
标记压缩:和标记清除算法基本相同,不同的就是,在清除完成之后,会把存活的对象向内存的一边进行压缩,这样就可以解决内存碎片问题。
当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。
永久代(元空间)
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间,Metaspace)的区域所取代。
值得注意的是:元空间并不在虚拟机中,而是使用本地内存(之前,永久代是在jvm中)。这样,解决了以前永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出,毕竟是和老年代共享堆空间。java8后,永久代升级为元空间独立后,也降低了老年代GC的复杂度。
Visual GC插件
自己不妨写点代码,测试下上面说过的GC过程,通过Visual GC插件
Java VisualVM安装Visual GC插件
https://blog.csdn.net/yujianping_123/article/details/99549194
面说到了minor gc 和major gc,那么看下full gc
Full GC
是清理整个堆空间—包括年轻代和老年代。
什么时候触发:
1. 调用System.gc
2. 方法区空间不足
2.老年代空间不足,包括:
新创建的对象都会被分配到Eden区,如果该对象占用内存非常大,则直接分配到老年代区,此时老年代空间不足
做minor gc操作前,发现要移动的空间(Eden区、From区向To区复制时,To区的内存空间不足)比老年代剩余空间要大,则触发full gc,而不是minor gc
等等
GC优化的本质,也是为什么分代的原因:减少GC次数和GC时间,避免全区扫描。
如何减少GC出现的次数(GC优化)
1.对象不用时最好显式置为 Null
一般而言,为 Null 的对象都会被作为垃圾处理,所以将不用的对象显式地设为 Null,有利于 GC 收集器判定垃圾,从而提高了 GC 的效率。
2.尽量少用 System.gc()
此函数建议JVM 进行主GC,虽然只是建议而非一定,但很多情况下它会触发主 GC,从而增加主 GC 的频率,也即增加了间歇性停顿的次数。
3.尽量少用静态变量
静态变量属于全局变量,不会被 GC 回收,它们会一直占用内存。
4.尽量使用 StringBuffer, 而不用String 来累加字符串。
由于 String 是固定长的字符串对象,累加 String 对象时,并非在一个 String对象中扩增,而是重新创建新的 String 对象,如 Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的 String 对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用 StringBuffer 来累加字符串,因 StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
5.分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM 在面临这种情况时,只能进行主 GC,以回收内存或整合内存碎片,从而增加主 GC 的频率。
集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主 GC 的机会。
6.尽量少用 finalize 函数。
因为它会加大 GC 的工作量,因此尽量少用finalize 方式回收资源。
7.如果需要使用经常用到的图片,可以使用软引用类型,它可以尽可能将图片保存在内存中,供程序调用,而不引起 OutOfMemory。
8.能用基本类型如 int,long,就不用 Integer,Long 对象
基本类型变量占用的内存资源比相应包装类对象占用的少得多,如果没有必要,最好使用基本变量。
9.增大-Xmx
以上是关于jvm之年轻代(新生代)老年代永久代以及GC原理详解GC优化的主要内容,如果未能解决你的问题,请参考以下文章