深刻理解Java之垃圾回收

概述

因为JVM中垃圾收集器的存在,使得Java程序员在开发过程当中能够不用关心对象建立时的内存分配以及释放过程,当内存不足时,JVM会自动开启垃圾收集线程,进行垃圾对象的回收。
那么垃圾回收线程究竟是何时触发,并如何实现垃圾回收的呢?本文将对openjdk的源码进行分析,并经过代码分析Java垃圾回收的过程。java

VMThread

VMThread主要负责调度执行虚拟机内部的VM线程操做,如GC操做等,在JVM实例建立时进行初始化。
这里写图片描述程序员

VMThread::create()

VMThread::create()方法负责该线程的建立。
这里写图片描述web

在create方法里主要执行两个事情:算法

  1. VMThread内部维护了一个VMOperationQueue类型的队列,用于保存内部提交的VM线程操做VM_operation,在VMThread建立时会对该队列进行初始化。
  2. 因为VMThread自己就是一个线程,启动后经过执行loop方法进行轮询操做,从队列中按照优先级取出当前须要执行的VM_operation对象并执行。

其中整个现成的轮询过程分为两步:
第一步
这里写图片描述安全

若是队列为空,_vm_queue->remove_next()方法则返回空的_cur_vm_operation,不然根据队列中的VM_operation优先级进行从新排序,并返回队列头部的VM_operation。若是_cur_vm_operation为空,则执行以下逻辑:
这里写图片描述并发

经过执行VMOperationQueue_lock->wait方法等待VM operation。jvm

第二步
这里写图片描述svg

若是当前vm_operation须要在安全点执行,如FULL GC,则执行上述逻辑,不然执行如下逻辑:
这里写图片描述
经过evaluate_operation执行当前的_cur_vm_operation,最终调用vm_operation对象的evaluate方法。
这里写图片描述
子类经过重写VM_Operation类的doit方法实现具体的逻辑。oop

Java gc触发

在Java的内存分配机制中,当新生代不足以分配对象所需的内存时,会触发一次YGC,具体实现以下:
这里写图片描述post

上面这段代码的意思是建立一个VM_GenCollectForAllocation类型的VM_Operation,经过执行VMThread::execute方法保存到VMThread的队列中,其中execute的核心实现以下:
这里写图片描述

YGC的VM_Operation加入到队列后,经过执行VMOperationQueue_lock的notify方法唤醒VMThread线程,等待被执行,其中VM_GenCollectForAllocation的doit方法实现:
这里写图片描述

经过VMThread调度执行gc操做,最终调用对应的doit方法:
一、利用SvcGCMarker通知minor gc操做的开始;
二、设置触发gc的缘由为GCCause::_allocation_failure,即内存分配失败;
三、其中GenCollectedHeap的satisfy_failed_allocation方法会调用GC策略的satisfy_failed_allocation方法,处理内存分配失败的状况;

satisfy_failed_allocation
这里写图片描述
若是其它线程触发了gc操做,则经过扩展内存代的容量进行分配,最后无论有没有分配成功都返回,等待其它线程的gc操做结束;

这里写图片描述

若是增量式gcincremental collection可行,则经过do_collection方法执行一次minor gc,即回收新生代的垃圾。

这里写图片描述
若是增量式gc不可行,则经过do_collection方法执行一次full gc。
这里写图片描述

gc结束以后,再次从内存堆的各个内存代中依次分配指定大小的内存块,若是分配成功则返回,不然继续。

这里写图片描述
若是gc结束后仍是分配失败,说明gc失败了,则再次尝试经过容许扩展内存代容量的方式来试图分配指定大小的内存块。
这里写图片描述
若是执行到这一步,说明gc以后仍是内存不足,则经过do_collection方法最后再进行一次完全的gc,回收全部的内存代,对堆内存进行压缩,且清除软引用。
这里写图片描述

通过一次完全的gc以后,最后一次尝试依次从各内存代分配指定大小的内存块。

:从上述分析中能够发现,gc操做的入口都位于GenCollectedHeap::do_collection方法中,不一样的参数执行不一样类型的gc。

这里写图片描述

do_collection实现

这里写图片描述

执行gc操做必须知足四个条件:
一、在一个同步安全点,VMThread在调用gc操做时会经过SafepointSynchronize::begin/end方法实现进出安全区域,调用begin方法时会强制全部线程到达一个安全点;
二、当前线程是VM线程或并发的gc线程;
三、当前线程已经得到内存堆的全局锁;
四、内存堆当前_is_gc_active参数为false,即还未开始gc;

这里写图片描述

若是当前有其它线程触发了gc,则终止当前的gc线程,不然继续。

这里写图片描述

根据参数do_clear_all_soft_refs和GC策略判断本次gc是否须要清除软引用;记录当前永久代的使用量perm_prev_used;若是启动参数中设置了-XX:+PrintHeapAtGC,则打印GC发生时内存堆的信息。

这里写图片描述

一、设置参数_is_gc_active为真,表示当前线程正式开始gc操做;
二、判断当前是否要进行一次full gc,并肯定触发full gc的缘由,如经过调用System.gc()触发;
三、若是设置了PrintGC和PrintGCDateStamps,则在输出日志中添加时间戳;
四、若是设置了PrintGCDetails,则打印本次gc的详细CPU耗时,如 user_time、system_time和real_time;
五、gc_prologue方法在gc开始前作一些前置处理,如设置每一个内存代的_soft_end字段;
六、更新发生gc的次数_total_collections,若是当前gc是full gc,则还需更新发生full gc的次数_total_full_collections;

这里写图片描述

获取当前内存堆的使用量gch_prev_used;初始化开始回收的内存代序号starting_level,默认为0,即从最年轻的内存代开始;若是当前gc是full gc,则从最老的内存代开始向前搜索,找到第一个可收集全部新生代的内存代,稍后从该内存代开始回收;

这里写图片描述

从序号为starting_level的内存代开始回收;若是当前内存代不须要进行回收,则处理下一个内存代,不然对当前内存进行回收;若是当前内存代全部内存代中最老的,则将本次的gc过程升级为full gc,更新full gc的次数,并执行full gc的前置处理。

这里写图片描述

一、若是设置了参数HeapDumpBeforeFullGC,则对内存堆进行dump;
二、若是设置了参数PrintClassHistogramBeforeFullGC,则打印在进行FGC以前的对象;

这里写图片描述

一、统计各个内存代进行gc时的数据;
二、若是开启了ZapUnusedHeapArea,则在回收每一个内存代时都要对内存代的内存上限地址top进行更新;

这里写图片描述

到这一步才开始真正的gc操做:设置当前内存代的_saved_mark值,即设置这些内存区域块的上限地址;经过每一个内存代管理器的collect方法对垃圾对象的进行回收,垃圾收集算法的具体细节会在后文进行分析;

这里写图片描述

一、若是当前是FGC,则调用post_full_gc_dump方法通知gc已经完成,能够进行后续操做,若是设置了参数HeapDumpAfterFullGC,则在gc后能够对堆内存进行dump;若是设置了参数PrintClassHistogramAfterFullGC,则在gc后能够打印存活的对象;
二、若是设置了参数PrintGCDetails,则在gc后能够打印内存堆的变化状况;若是当前仍是FGC,则还能够打印永久代的内存变化状况。

这里写图片描述

gc完成后,调整内存堆中各内存代的大小;若是是FGC,则还须要调整永久代大小;获取FullGCCount_lock锁,对_full_collections_completed进行更新,并经过锁机制通知本次FGC已经完成;

这里写图片描述

打印内存堆的gc总次数和FGC次数;ExitAfterGCNum默认是0,若是设置ExitAfterGCNum大于0,且gc的总次数超过ExitAfterGCNum,则终止整个JVM进程。到此Java jvm垃圾回收进程就终止gc进程。

本文同步分享在 博客“xiangzhihong8”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索