Java GC 了解

  • GC 是什么和为什么?

  • 我们知道,java代码在执行过程中,会在堆上生成一个个的对象,即使该线程的代码执行结束,只要jvm不重启,堆上的Java对象就不会消失。这样子,随着创建的对象越来越多,就有可能出现把内存挤爆的情况。
  • 在C ++ 中,程序员可以用delete/free的指令来回收这些生成的对象,手动回收有个缺点就是程序员忘了delete/free,那么这个对象就会一直存在,如果程序员记性不太好,那么也还是会出现内存被挤爆的情况。
  • 那么要怎么处理这些对象呢?java的做法是雇个清洁工,在某些特定的时间就会把堆上那些没有用到的对象给回收掉。这样子就避免了内存被挤爆的情况了。
  • 这个清洁工就是GC(Garbage Collection,垃圾收集器)。
  • 回收什么

GC回收的是那么死了的对象,即没有被引用到的对象。

怎么判断一个对象是否有被引用(是否存活)?
  1. 引用计数算法 : 每个对象都有一个计数器,当对象被引用的时候计数器 +1,当对象失去引用的时候计数器 -1。
    这个算法不能很好的解决对象互相引用的问题,如下面的例子,如果使用的是引用计数法,方法(线程)run执行结束后,a对象持有b的引用,b对象持有a的引用,除此以外,a,b没有被任何地方引用到,但是因为a,b的计数器都不为0,所以没法被回收掉。
class Obj{
  public Object o;
}

public void run(){
  Obj a = new Obj();
  Obj b = new Obj();
  a.o = b;
  b.o = a;
}
  1. 可达性分析算法



    做一个很暴力的比喻,假如我们把Java堆比作一面悬崖,对象就是挂在悬崖上或者掉到河里的人,挂在悬崖上的是活着的对象,掉到河里的是死了的对象;我们要怎么找到还活着的对象呢?我们只需要顺着铁环找下去即可。
    给可达性算法下一个定义就是: 通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

GC roots

GC Roots可以理解成我们悬崖比喻中的铁环。那什么东西可以作为GC Roots呢?

  1. 虚拟机栈中的引用对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中引用的对象
再谈引用

实际上并不是所有GC roots能找到的引用的对象就不会被回收掉。Java把引用分为4中:

  • 强引用
    强引用类似于Object o = new Object()这样的引用,这样的引用如果能被找到就不会被回收掉。
  • 软引用
    当jvm将要发生内存溢出的时候,软引用才会被回收。
    public static void main(String[] args) {
        String s = new String("hello world");
        SoftReference reference = new SoftReference(s);
    }

用伪代码来表示就是:

  if(JVM内存不足){
    str = null;
    System.gc();
  }
  • 弱引用
    在下一个gc时,弱引用都会被回收掉。可以使用WeakReference来指代一个弱引用。
  • 虚引用
    形同虚设的引用。
  • 在哪里回收

我们上面说的都是对象回收就是在堆上进行的。

方法区

方法区也会发生GC。主要回收废弃常量和无用的类。

  1. 废弃常量
    比如字符串常量池有“abc”,但是实际上没有任何地方引用了“abc”,所以如果发生gc的话,“abc”就会被回收掉。
  2. 无用的类
  • 该类已经没有对应的class对象
  • 该类已经没有任何实例
  • 该类的classloader已经被回收了
  • 什么时候回收

  1. 当代码显性调用 System.gc()
  2. 当Eden内存不足(发生young gc)或老年代内存不足(发生old gc)
  • 怎么回收

复制算法

定义 将可用内存分成大小相同的两块,每次只使用其中一块。当一块内存用完以后,就将还存活的对象复制到另一块上面去,然后再把原来的内存全部清理掉。
不足 内存只有一半的使用率。
优化 经研究发现,每次GC中不需要清理的对象平均只占了全部对象的10%不到,所以我们可以把内存分为三个部分Eden(80%),survivor1(10%),survivor2(10%)(这个80%,10%是Hotspot默认的),新对象放到Eden上,当Eden的内存装满以后,把活着的对象放到survivor1中,当Eden内存再次装满以后,把Eden和survivor1的或者的对象放到survivor2中,当Eden再再再装满以后,把Eden和Survior2的活着的对象重新装回survivor1。
新生代和老年代 那可能会有这样一种情况,即Eden和某一个Survior中活着的对象超过了10%,另外一个Survior已经装不下了怎么办?Java又发明了一块内存区域,用来装某次复制中超过10%的那活着的对象中最久的那部分对象(默认已经经过了15次复制),这块内存区域就叫做老年代,而我们Eden+Survior1+Survior2就叫做新生代。因为新生代和老年代存放的对象存活时间不相同,所以一般采用不同的算法分别对它们进行回收。

标记-清除算法

老年代的对象存活一般比较久,并且已经没有其它内存区域可以作为担保了,所以不太适合用复制算法,一般使用标记-清除算法或者标记-整理算法。
定义 : 先标出所有需要回收的对象,在标记完成后统一清除所有被标记的对象。
不足 1. 效率太低 2. 会产生内存碎片

标记整理算法

定义 先把要过期的对象标记出来,然后再向一端整理,然后把边界以外的对象都清理掉。

  • 垃圾回收器

上面介绍的怎么回收对象,不管是复制算法,还是标记-清除或者标记-整理,都只是算法,而垃圾回收器就是它们的实现。

ParNew

发生在新生代的垃圾回收器,采用复制算法,是serial的多线程版本,每次收集的时候会暂停所有其他工作进程,直到垃圾清理结束。

CMS

发生在老年代的垃圾回收器,系统停顿时间极短,采用升级版的标记-清除算法(包括多次GC后会合并内存碎片等)。一般CMS的回收会有以下几个步骤:

  1. 初始标记
  2. 并发标记
  3. 并发预清理
  4. 重新标记
  5. 并发清除
  6. 重置
G1
  • GC日志分析

# 因为Eden不够装了,发生了新生代gc,时间为2018-06-13T16:14:19.725,垃圾回收器是ParNew,新生代由928313K->30724K,耗时0.3秒,堆内存由1070687K->173098K
2018-06-13T16:14:19.725+0800: 3706.509: [GC (System.gc()) 2018-06-13T16:14:19.725+0800: 3706.509: [ParNew: 928313K->30724K(1887488K), 0.3284640 secs] 1070687K->173098K(8183040K), 0.3286625 secs] [Times: user=1.14 sys=0.86, real=0.33 secs]
# 上面的新生代gc把对象放到老年代,因为老年代不够装,所以发生老年代gc,这是cms的第一个步骤,初始标记
2018-06-13T16:14:20.054+0800: 3706.838: [GC (CMS Initial Mark) [1 CMS-initial-mark: 142373K(6295552K)] 173934K(8183040K), 0.0195301 secs] [Times: user=0.04 sys=0.01, real=0.02 secs]
# 并发标记
2018-06-13T16:14:20.074+0800: 3706.858: [CMS-concurrent-mark-start]
2018-06-13T16:14:20.111+0800: 3706.895: [CMS-concurrent-mark: 0.036/0.037 secs] [Times: user=0.29 sys=0.05, real=0.03 secs]
# 并发预清理
2018-06-13T16:14:20.111+0800: 3706.895: [CMS-concurrent-preclean-start]
2018-06-13T16:14:20.126+0800: 3706.910: [CMS-concurrent-preclean: 0.015/0.015 secs] [Times: user=0.02 sys=0.01, real=0.02 secs]
2018-06-13T16:14:20.126+0800: 3706.910: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 2018-06-13T16:14:25.170+0800: 3711.954: [CMS-concurrent-abortable-preclean: 3.443/5.045 secs] [Times: user=3.48 sys=0.05, real=5.04 secs]
# 重新标记
2018-06-13T16:14:25.171+0800: 3711.955: [GC (CMS Final Remark) [YG occupancy: 67104 K (1887488 K)]2018-06-13T16:14:25.171+0800: 3711.955: [Rescan (parallel) , 0.0660326 secs]2018-06-13T16:14:25.237+0800: 3712.021: [weak refs processing, 0.0392816 secs]2018-06-13T16:14:25.276+0800: 3712.060: [class unloading, 0.0240555 secs]2018-06-13T16:14:25.300+0800: 3712.084: [scrub symbol table, 0.0089929 secs]2018-06-13T16:14:25.309+0800: 3712.093: [scrub string table, 0.0011297 secs][1 CMS-remark: 142373K(6295552K)] 209478K(8183040K), 0.1476794 secs] [Times: user=1.11 sys=0.05, real=0.15 secs]
# 并发清理
2018-06-13T16:14:25.319+0800: 3712.103: [CMS-concurrent-sweep-start]
2018-06-13T16:14:25.444+0800: 3712.228: [CMS-concurrent-sweep: 0.121/0.126 secs] [Times: user=0.14 sys=0.13, real=0.13 secs]
# 并发重置
2018-06-13T16:14:25.444+0800: 3712.228: [CMS-concurrent-reset-start]
2018-06-13T16:14:25.485+0800: 3712.269: [CMS-concurrent-reset: 0.041/0.041 secs] [Times: user=0.03 sys=0.04, real=0.04 secs]

你可能感兴趣的:(Java GC 了解)