转载、引用请标明出处
https://www.jianshu.com/p/7c6c9507e48f
本文出自zhh_happig的简书博客,谢谢
以下内容,是本人学习的笔记和工作中的总结,仅供大家参考,有误的地方还请指正
一 Java内存分配:分代分配
对象将根据存活的时间被分为:
- 年轻代(Young Generation)
- 年老代(Old Generation)
- 永久代(Permanent Generation,也就是方法区)
1 年轻代(Young Generation)
- 对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
- 年轻代上内存分配
- 年轻代可以分为3个区域:Eden区(首次分配的区域)和两个存活区from、to(Survivor 0 、Survivor 1:大小相等)
- 内存分配过程为绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快
- 最初一次,当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的),清空Eden区
- 下次Eden区满了,再执行一次Minor GC,将消亡的对象清理掉,将存活的对象复制到Survivor1中,然后清空Eden区,将Survivor0中消亡的对象清理掉,将其中可以晋级的对象晋级到老年代区,将存活的对象也复制到Survivor1区,然后清空Survivor0区;
- 当两个存活区切换了几次之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代
- HotSpot虚拟机默认切换15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代
-
但这只是个最大值,并不代表一定是这个值;每次Minor GC,Survivor中的存活对象的age都加1,如果有一组age相同的对象,并且它们大于Survivor空间的一半,那么这一组对象都会被赋值到老年代,不一定要等到最大值
从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法。
2 年老代(Old Generation)
- 对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代
- 年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC。
- 对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上
- 用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。
- 可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。
- 解决的方法是,年老代中维护一个”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
3 方法区(永久代)
- 常量池,类信息
- 无垃圾收集行为,也可以被回收
二 Java GC机制:分代收集
1 年轻代--Young
- 每次进行GC清理时,将Eden区和一个Survivor中仍然存活的对象拷贝到 另一个Survivor中,然后清理掉Eden和刚才的Survivor。
- 由于绝大部分的对象都是短命的,甚至存活不到Survivor中,所以,Eden区与Survivor的比例较大,HotSpot默认是 8:1,即分别占新生代的80%,10%,10%,可通过jvm参数设置。
- 如果一次回收中,Survivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到 老年代。
- 用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor1:Survivor2=8:1:1
2 老年代--Tenured
- 老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。
- 老年代一般用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。
- 在年轻代发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,所以老年代不经常GC;可以看出年轻代发生Minor GC时,内存还是有空间的;老年代发生Full GC时,内存已经没有空间了
- Full GC,对整个堆进行整理,包括Young、Tenured和Perm,所以Full GC比较慢。频繁Full GC影响性能,有如下原因可能导致Full GC
- 年老代被写满
- 永久代被写满
- System.gc()被显示调用
-
在发生MinorGC前:先检查老年代是否有连续空间,如下图所示
- 大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁,但是也增加了OutOfMemoryError的风险。
- JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。
3 方法区(永久代--Perm)
- 永久代的回收有两种:常量池中的废弃常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收
- 对于无用的类信息进行回收(类的卸载),必须保证3点:
- 类的所有实例都已经被回收
- 加载类的ClassLoader已经被回收
- 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)
- 永久代的回收并不是必须的,可以通过jvm参数来设置是否对类信息进行回收
以上内容,是本人学习的笔记和工作中的总结,仅供大家参考,有误的地方还请指正
转载、引用请标明出处
https://www.jianshu.com/p/7c6c9507e48f
本文出自zhh_happig的简书博客,谢谢