# (一)关于垃圾回收 {#一-关于垃圾回收}
JAVA的垃圾回收需要完成三件事情:
1、哪些内存需要回收
2、什么时候回收
3、如何回收
下面就从这三个问题出发去了解Java的垃圾回收机制。
# (二)哪些垃圾需要回收 {#二-哪些垃圾需要回收}
在垃圾回收之前,首要的问题是确定哪些垃圾需要被回收,现在Java通过根搜索算法(GC Roots Tracing)来判断一个对象是否存活,这个算法的思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点向下搜索,当GC Roots到达不了这个某个对象时(或者说某个对象没有被任何其他对象所引用),就证明这个对象是不可用的,这些对象会被判定为需要回收的对象。
如图,ObjC是不可达的,这个对象就是需要被回收的对象。
在Java语言中,可作为GC Roots的对象包括下面这些:
1、虚拟机栈(栈帧中的本地变量表)中引用的对象
2、方法区中的类静态属性引用的对象
3、方法区中的常量引用的对象
4、本地方法栈(Native方法)引用的对象
# (三)什么时候回收 {#三-什么时候回收}
关于如何回收的问题,我参考了《深入理解Java虚拟机》,根搜索算法中不可达的对象,并不是立刻就会被回收,而是会经过一次标记:
如果对象没有覆盖finalize()方法,或者finalize()方法已经被调用,虚拟机会判定这个对象没必要执行finalize(),在这一次标记中该对象不会被回收。
如果这个对象被标记为有必要执行finalize()方法时,它会被放置在一个名为F-Queue的队列中,稍后由虚拟机进行垃圾回收。
但是这个对象还有最后一次逃脱的机会,当在F-Queue时,虚拟机会对F-Queue中的对象作小规模的标记,如果发现此时某个对象又可达了,就会逃过GC的命运。
# (四)如何回收 {#四-如何回收}
如何回收垃圾的问题归根结底就是垃圾回收算法如何回收垃圾的问题。这里主要介绍三种垃圾回收算法的执行思路:
# 4.1 标记-清除算法(Mark-Sweep) {#_4-1-标记-清除算法-mark-sweep}
这种算法分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收掉被标记的对象。
看图就可以明白了,这个算法的问题在于,清除之后会产生大量不连续的空间碎片。
# 4.2 复制算法(Copying) {#_4-2-复制算法-copying}
复制算法将内存分为两块,每次使用其中一块,垃圾回收时,将正在使用的那块内存中存活的内存放入另一块内存中,然后清空原内存块,图示图下:
复制算法被广泛应用于新生代的垃圾回收,由于新生代的对象有百分之98左右都是要被回收的,因此新生代的内存会被分为一块Eden空间和两块Survivor空间,比例为8:1:1。
第一次YGC 只回收eden区域,回收后大多数(百分之九十八左右)的对象会被回收,活着的对象通过复制算法进入Survivor0(后续用S0和S1代替)。再次YGC后 ,eden+S0中活着的对象进入S1。再次YGC,eden+S1中活着的对象进入到S0。依次循环
# 4.3 标记-整理(Mark-Compact) {#_4-3-标记-整理-mark-compact}
标记-整理算法分为标记、整理、清除三步,第一步也是标记出可回收的对象,然后让存活的对象移到一边,然后直接清理掉边界外的垃圾。
标记整理算法被广泛应用于老年代的垃圾回收。
# (五)何时触发YoungGC或FullGC {#五-何时触发younggc或fullgc}
YoungGC的触发时常在发生,当新生代的Eden区满了之后就会触发YoungGC。
FullGC在多个情况下都会被触发:
1、发生Young GC之前进行检查,如果"老年代可用的连续内存空间" < "新生代历次Young GC后升入老年代的对象总和的平均大小",说明本次Young GC后可能升入老年代的对象大小,可能超过了老年代当前可用内存空间,此时会触发FullGC
2、当老年代没有足够空间存放对象时,会触发一次FullGC
3、如果元空间区域的内存达到了所设定的阈值-XX:MetaspaceSize=,也会触发FullGC。