关键问题:伊甸园和幸存区为什么都属于年轻代,而不是分开来算?幸存区为什么有两个?什么时候触发垃圾清理? 怎么样知道对象不在被引用?
伊甸园Edem
你是一个在伊甸园生活的小动物,每天都有新的小动物被上帝放进来,有一天有一只大象要进来,但是伊甸园实在装不下了,上帝就会发动大洪水,把那些已经完成使命的小动物冲走(确定有没有完成使命也很简单,直接叫主人来认)。你虽然完成了使命,但是仍然身手敏捷躲过一劫,藏进了幸存区;这是妖精修炼的地方,你来的时候看到还有其他的妖精已经在里面了。
新创建的对象优先存放在年轻代的伊甸园区,存放满了就会触发Minor GC清理,清理年轻代中不再被引用到的对象(可达性分析:从GC Roots开始向下收索,搜索走过的路径成为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。当然也有其他的算法);有一些清理不掉的就移入幸存区。
幸存区Survivor
幸存区是个神奇的地方,自由伊甸园的1/8(默认),传闻幸存区有两个,它们一模一样,一个出现的时候另一个隐藏;每当洪水来临它们就会互相交换位置,而在这里修炼的妖精如果没有及时找到另一个幸存区,也会被大洪水冲走。
这一天大洪水又来了,大家都在着急寻找另一个幸存区的时候,你发现老虎精一点也不慌张,原来他已经渡劫了15次,可以进入魔界了;这时大洪水把那些还没找到幸存区的妖精全部冲走了,再另一个幸存区你看到有刚和你一起逃过来的妖精和一些新面孔,这些新面孔是从伊甸园逃过来的动物。
幸存区有两个,但只用一个,另一个要保持空着,每次清理的时候切换幸存区;空着是为了装下次清理不掉的垃圾,还有给老垃圾计数,默认进入老年代的阈值是15次;每个幸存区的默认大小是伊甸园的1/8。
老年代 Old Gen
终于你也历经了15次劫难,进入了魔界;这个地方时间过得非常慢;外界轮回了几百次,这里还是老样子,偶尔有一些入魔的妖精进来。
不知过去了多久,这一天魔界有了大动静,上帝造了一只鲲,地界用洪水冲了一遍还是放不下;直接放进了魔界,虽然魔界是地界定两倍(默认),但是这个鲲还是太大了;魔界很快就将会被装满了。
果不其然。上帝在用大洪水清理完地界后,有一些妖精进入魔界时发生了点意外,原来魔界终于满了;由于魔物对洪水已经免疫力,所以上帝使用了圣光裁决对魔界进行清洗;花费了漫长的时间,是洪水的10倍;圣光裁决之前必然会发生大洪水。
不知道什么原因,你又侥幸的活了下来;但是大部分魔物都被清理掉了,看着空荡荡的魔界,又可以过很长一段时间的悠闲日子了。
老年代的操作没那么频繁,默认大小是年轻代的两倍;年轻代装不下的对象也会直接存放在老年代;老年代内存满了会调用Major GC清理老年代,但是时间比清理年轻代要长10倍;如果老年代也装不下就会报异常,内存溢出OOM。
整体收集 Full GC
就这样过了很久很久,魔界又快满了;地界有一只大象修炼成魔了,但是魔界又容不下他,下不去又上不来;惊动了上帝,出现了这种不合规定存在,只能启动审判日,清理整个世界!审判日持续时间会很长很长,大多数情况都要避免使用审判日。
关于整堆收集:每次开始老年代收集之前必定会先触发年轻代收集,所以清理老年代的时候基本等于整堆收集了,所以Full GC=Young GC+Old GC?
Full GC还是和Young GC+Old GC有点区别,如果执行Young GC时需要移动到老年代的对象大于老年代剩余空间,这种情况就只有Full GC可以解决。
调用System.gc()也会执行Full GC。
内存溢出(out of memory)OOM
本来以为世界清静了,可惜世事难料,上帝一次性造了3只鲲,地界和魔界都装不下了,世界毁灭了,OOM。
GC算法
没用在java中的引用计数法
对象被引用+1,解除引用-1;碰上互相引用就没法处理了。
可达性分析算法,大佬认领法
使用GCRoots(下面会有介绍),GC根集合,从根出发,寻找被引用的对象;剩下的没被引用的就是垃圾。
标记法, 有了GCRoots怎么标记对象?
从根开始寻找引用的对象,还有引用对象引用的对象。
多标/漏标:由于大部分GC都是并行发生,所以有些对象正在被使用,标记的时候被引用,标记结束后引用已经解除了,这就是多标,反过来叫漏标;多标下次GC再清理就好,而漏标的危害更大,被引用的对象当作垃圾清理,会出现严重问题。
重标:就是在完成一次标记后,STW(全部暂停),再检查一边;由于之前已经并行完成了大多数工作,所以暂停时间不会很长。
不管是哪种收集器都会这样。
标记之后就该清理了
清除算法
最快,最不负责任的算法;简单的把垃圾删了就行。缺点:导致有很多碎片。
复制算法
复制算法需要有一块空内存,就像幸存1区和幸存2区一样;删除垃圾后,把存活对象移到新空间。缺点:占两倍空间。
整理算法
删除之后再整理。缺点:费时间,复杂度高。
G1收集器,垃圾回收的平行世界
把内存分成很多区块,每块都有各自的年轻带/老年带,充分利用了多核CPU,硬件进步带来的好处;
整体使用整理法,局部使用复制法(白色空内存就可以使用复制法),没有碎片。
计算分区垃圾量,回收多的。
Humongous是专门存大件的。
被淘汰的CMS收集器
使用标记清除法,导致有很多碎片,最后不得不使用Full GC来处理,漫长的停顿。对CPU敏感。
其他
GC垃圾回收在JAVA8以后只有青年代和老年代,之前还有永久代。
收集器:
年轻代收集(Minor GC / Young GC): 只是年轻代的垃圾收集
老年代收集 (Major GC / Old GC): 只是老年代的垃圾收集 (CMS GC 单独回收老年代)
混合收集(Mixed GC):收集整个新生代及老年代的垃圾收集 (G1 GC会混合回收, region区域回收)
整堆收集(Full GC):收集整个java堆和方法区的垃圾收集器
GC Roots的对象有:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象。
方法区中常量引用的对象。譬如字符串常量池里的引用
本地方法栈中引用的对象
所有被同步锁持有的对象
分代收集时,将其他代的引用对象临时性加入GC Roots