上次咱们提到了垃圾回收(将内存中不再被使用的对象进行回收),GC(Garbage Collection)分为两种,一种是很快的对新生代对象收集的叫minor GC,另一种是很慢的对旧生代对象收集叫Full GC,触发Full GC可以使用System.gc()。
是怎么收集不用的内存呢?垃圾收集算法,第一种最低效的叫,标记清除算法,首先是他标记清除的效率都不高,另外,看图上,清除后会留下不连续空白,如果不是最后几个内容空间被清除,一旦有大对象需要连续的内存空间存的时候,会发现没地方存的下,导致提前触发下一次gc。
有了这个问题,咱们可以接着看下一个算法,复制算法,步骤是这样的,首先把内存划分为两块完全相同的部分,前面和上面的标记清除算法一致,但是回收之后做了一个操作,将上面部分清除后存活的对象全部复制到了下面,达到没有连续空白空间的作用。问题也很明显,可以使用的内存变成了原来的一半,另外还有一个多了一个复制的时间。
我们继续带着问题往下看下面这个算法,标记整理算法,步骤是这样的,先标记需要清除的内存,让存活的对象往一端移动,清除掉存活对象之外的空间,让前面内存不留空白。
有了垃圾收集算法的基础,我们可以开始了解下,堆内存的分代是怎么划分的了,下面这个图可以很清楚的看到堆内存各代的划分,其中持久代(永久存储区)主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代(新生区)和年老代(养老区)的划分是对垃圾收集影响比较大的。
新生代:每次GC时都会有大量对象死去,少量存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。
年老代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。
持久代:用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。
接着咱们来看看他们新生代和年老代间是怎么转换,
我们直接新new的对象最开始是在Eden区的,和他的名字一样,伊甸园,一起梦开始的地方哈哈哈,Eden区满了咋办?这时候接着用其中一个Survivor from区,也叫存活区,如果Survivor from也满了怎么办?这时候就触发新生代的GC(Minor GC前面提到过是新生代的时候触发的),这时候会有一堆对象死掉,被释放掉,还会有存活的对象,将Eden和Survivor from中存活的对象一起复制到另一个Survivor to中,然后清空Eden和Survivor from区,接着重新开始给Eden区存新对象,Eden满了在给Survivor to存。发现了没,精妙吧,竟然形成了一个循环,每次都有一个Survivor 是空的,Eden和另一个Survivor 在存,满了触发GC复制到空的Survivor 里无限循环,太精秒了。
新生代到老年代的转换方法:
1.那么问题来了,如果,空的那个Survivor 容纳不了刚才Eden和另一个不空的Survivor GC后存活的对象怎么办?别担心,老年代再给你兜底呢,直接将这一堆放不下的对象放老年代里,如果老年代也放不下了,触发Full GC(老年代GC)。
2.新生代给老年代转换,不仅可以上面存不下在转,还有几种方式,JVM其实给每个对象都有一个年龄(逃过GC的次数)的计数器,从Eden出生,第一次Minor GC没把对象干掉,移到Survivor区后年龄就加1,以后每次逃过一次Minor GC,年龄就加1,默认是15岁(通过XX:MaxTenuringThreshold来设定),到这年龄就算资历很深了,可以算老人了,进入到老年代。
3.还有个进入老年代的机会,如果Survivor 空间中相同年龄所有对象的大小的总和大于Survivor的大小一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。
4.另外,很大的对象是可以直接进入老年代的,在JVM中有个参数配置-XX:PretenureSizeThreshold,只要对象大小大于这个设置的值,直接进入老年代,避免了在Eden和Survivor区之间发生大量的内存复制,咋样精妙吧,这设计可以吧哈哈哈。
最后,大家想获取更多知识的,可以继续关注公众号,不定时推送。分享了这么牛逼的知识,还不请小编喝个水吗,哈哈哈,欢迎土豪直接赏赞,谢谢,您的支持就是小编最大的动力。