Java的GC如何玩弄对象

Java的GC是什么,应该做Java的人都知道。但是其实GC的历史要比Java早,Java出现之前,人们就开始研究:

  • 哪些内存需要回收
  • 哪些内存什么时候回收
  • 怎样回收

然后就有了GC,而Java解决的这3件事情,就目前看来,效果还可以。解决了很多的内存问题。但是,GC并不能解决所有内存动态分配的问题,尤其是高并发的软件中,了解GC,方便解决内存溢出问题,更好的控制和调节程序的回收和分配内存。

I 对象生死判定算法

感叹一下乔布斯当年看中的两项技术Internet 和OOP Language,现在都如日中天,不愧是乔帮主。

Java是OOP的经典语言之一,OOP语言号称万物皆对象,对象用不到了,那就应该离开内存了,这就是对象的死亡。是不是死掉的呢

死掉的对象,自然需要GC来处理,但是GC怎么知道对象已经死掉了呢?

1x01 引用计数算法

就是给每个对象添加一个引用计数器,当对象被引用一次,计数器就+1;引用失效时,计数器就-1。当计数器为0,就说明对象死了。这个方法实现简单,效率也可观。但是,主流的JVM没用这个算法,因为这个算法很难解决循环引用的问题。

什么是循环引用?
就是多个对象,互相引用对方作为属性,下面就是A依赖B,B依赖C,C依赖A的循环引用。
这就是,百度一下就知道了

当然,还有更细致的分法:

  • 强引用
    String a = new String()就是这样的,强引用存在,GC就不能回收对象
  • 软引用
    有用单非必须的对象,这些对象在内存快溢出就会被回收,回收内存依旧不够才会抛出异常
  • 弱引用
    比软引用还非必要,每次GC来的时候都会被回收
  • 虚引用
    最弱的引用,无法通过这个引用来获取对象,唯一的作用是在被回收事给系统一个通知

1x02 可达性算法

这个比上面高大上一点,Java通过可达性分析来判定对象是否还被引用。什么的可达性分析呢:
Java会从一些叫做GCRoot的对象开始向下遍历,可以遍历到的对象,就是被引用的对象,不可以遍历到的对象就是不可达对象,就是死掉的对象了:

蓝色表示可达对象
灰色表示不可达对象
蓝色表示可达对象灰色表示不可达对象

在图上可以看到,从GCRoot开始,蓝色部分的对象都可以被遍历到,儿灰色部分,即使 Object A 可以遍历到 Object B 和Object C,但是却没有了GCRoot 引用,所以就属于不可达的死亡对象了

(是不是找不到对象就可以死了T^T)。

GCRoot 包括:栈中引用对象,方法区静态引用对象,方法区常量引用对象,本地方法引用对象(Native层的)

II GC回收垃圾的算法

既然已经能判断了垃圾有哪些,接下来就简单讲讲对垃圾对象如何清理

2x01 标记-清除算法

跟名字一样,先把死掉的对象标记出来,然后清除,大部分算法是基于这个思想,不足之处也很明显,1是效率问题,标记和清除的过程都慢,2是空间问题,清除之后会带啦大量的不连续碎片空间。小的碎片会放不下大对象,导致大对象创建时又会触发一次回收

回收前
回收前

回收后
回收后

2x02 复制算法

为了解决效率问题,有了复制算法,这种算法将内存分成相同大小的两块

回收前
回收前

回收后
回收后

其实并不是非要等比划分内存的,大部分对象死的很早Hotspot是划分了三块区域,一块大的两块小的,大的叫Eden,小的叫survivor,大小比例为8:1。清理时将Eden和survivor中存活的对象复制到另一块survivor内存上,然后,清理掉用过的两块内存,下次再用。当survivor不够大的时候,需要依靠新的分配担保去拓展空间。

2x03 标记-整理算法

综合复制和标记算法,整理算法会把有用的存活对象向y,一端移动,这样避免了复制算法浪费那么多内存,也不会像普通标记回收算法一样导致内存碎片过于严重。

回收前
回收前

回收后
回收后

2x04 分代收集算法

将java堆内存分成老年代,新生代。根据经验,新生代死亡比较快,老年代比较持久。所以一般新生代区域使用复制方法,只需要复制几个就可以了,老年代比较持久,所以一般用标记清除,或标记整理来回收。

III 小结

GC是Java中最诱人的处理内存的方式,也是最令人难受的处理方式。想要深入java,GC是绕不过的必经之路。了解GC的运作方法,可以帮助程序员处理更深层次的Java问题,做出更深层次的系统优化。希望我的小总结能给你带来帮助

转载请注明出处。https://micorochio.github.io/2017/03/31/How-Java-GC-Play-with-Memery/
ps我的博客:https://micorochio.github.io/