在什么时候:
首先需要知道,GC又分为minor GC 和 Full GC(major GC)。Java堆内存分为新生代和老年代,新生代
中又分为1个eden区和两个Survior区域。
一般情况下,新创建的对象都会被分配到eden区,这些对象经过一个minor gc后仍然存活将会被移动到
Survior区域中,对象在Survior中没熬过一个Minor GC,年龄就会增加一岁,当他的年龄到达一定程度时,
就会被移动到老年代中。
当eden区满时,还存活的对象将被复制到survior区,当一个survior区满时,此区域的存活对象将被复制到另外一个
survior区,当另外一个也满了的时候,从前一个Survior区复制过来的并且此时还存活的对象,将可能被复制到老年代
因为年轻代中的对象基本都是朝生夕死(80%以上),所以年轻代的垃圾回收算法使用的是复制算法,
复制算法的基本思想是将内存分为两块,每次只有其中一块,当这一块内存使用完,就将还活着的对象复制到
另一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于eden区,和名为“From”的Survior区,Survior区“to”是空的。紧接着GC
eden区中所有存活的对象都会被复制到“To”,而在from区中,仍存活的对象会根据他们的年龄值来决定去向,
年龄到达一定只的对象会被复制到老年代,没有到达的对象会被复制到to survior中,经过这次gc后,eden区和from
survior区已经被清空。这个时候,from和to会交换他们的角色,也就是新的to就是上次GC前的from
Minor GC:从年轻代回收内存
当jvm无法为一个新的对象分配空间时会触发Minor GC,比如当Eden区满了。
当内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden和Survior区不存在内存碎片
写指针总是停留在所使用内存池的顶部。执行minor操作时不会影响到永久代,从永久带到年轻代的引用被当成
GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉(永久代用来存放java的类信息)。如果eden区域中大部分
对象被认为是垃圾,永远也不会复制到Survior区域或者老年代空间。如果正好相反,eden区域大部分新生对象不符合GC
条件,Minor GC执行时暂停的线程时间将会长很多。Minor may call "stop the world";
Full GC:是清理整个堆空间包括年轻代和老年代。
那么对于Minor GC的触发条件:大多数情况下,直接在eden区中进行分配。如果eden区域没有足够的空间,
那么就会发起一次Minor GC;对于FullGC的触发条件:如果老年代没有足够的空间,那么就会进行一次FullGC
在发生MinorGC之前,虚拟机会先检查老年代最大可利用的连续空间是否大于新生代所有对象的总空间。
如果大于则进行Minor GC,如果小于则看HandlePromotionFailure设置是否是允许担保失败(不允许则直接FullGC)
如果允许,那么会继续检查老年代最大可利用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于
则尝试minor gc (如果尝试失败也会触发Full GC),如果小于则进行Full GC。
但是,具体什么时候执行,这个是由系统来进行决定的,是无法预测的。
对什么东西:
主要根据可达性分析算法,如果一个对象不可达,那么就是可以回收的,如果一个对象可达,那么这个对象就不可以回收,
对于可达性分析算法,它是通过一系列称为“GC Roots”的对象最为起始点,当一个对象GC Roots没有任何引用链相接的时候,
那么这个对象就是不可达,就可以被回收。
做了什么事情:
主要做了清理对象,整理内存的工作。Java堆分为新生代和老年代,采用了不同的回收方式。
例如新生代采用了复制算法,老年代采用了标记整理法。在新生代中,分为一个ede区域和两个Survior
区域,真正使用的是一个eden区域,和一个Survior区域,GC的时候,会把存活的对象放入到另一个Survior区域中,
然后再把这个eden区域和Survior区域清除。那么对于老年代,采用的是标记整理发,首先标记出存活对象,
然后在移动到一段。这样有利于减少内存碎片。
标记:标记的过程其实就是,遍历所有gc root 然后将所有gc root 可达的对象标记为存活对象
清除:清除的过程中将遍历堆中所有的对象,将没有标记的对象全部清除掉
主要缺点:标记和清除过程效率不高,标记清除之后会产生大量不连续的内存碎片
但是,老年代中因为对象存活率高,没有额外空间对他进行分配担保,就必须使用标记整理算法
标记整理算法 标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理
无用对象完成后让所有存活的对象都向一段移动,并更新其引用对象的指针
主要缺点:在标记清除的基础上还需要进行对象的移动,成本相对比较高,成本相对较高,好处是不会产生内存碎片。