深入理解Java之垃圾回收

概述

由于JVM中垃圾收集器的存在,使得Java程序员在开发过程中可以不用关心对象创建时的内存分配以及释放过程,当内存不足时,JVM会自动开启垃圾收集线程,进行垃圾对象的回收。

那么垃圾回收线程到底是什么时候触发,并如何实现垃圾回收的呢?本文将对openjdk的源码进行分析,并通过代码分析Java垃圾回收的过程。

VMThread

VMThread主要负责调度执行虚拟机内部的VM线程操作,如GC操作等,在JVM实例创建时进行初始化。

VMThread::create()

VMThread::create()方法负责该线程的创建。

在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。

第二步

如果当前vm_operation需要在安全点执行,如FULL GC,则执行上述逻辑,否则执行以下逻辑:

通过evaluate_operation执行当前的_cur_vm_operation,最终调用vm_operation对象的evaluate方法。

子类通过重写VM_Operation类的doit方法实现具体的逻辑。

Java gc触发

在Java的内存分配机制中,当新生代不足以分配对象所需的内存时,会触发一次YGC,具体实现如下:

上面这段代码的意思是创建一个VM_GenCollectForAllocation类型的VM_Operation,通过执行VMThread::execute方法保存到VMThread的队列中,其中execute的核心实现如下:

YGC的VM_Operation加入到队列后,通过执行VMOperationQueue_lock的notify方法唤醒VMThread线程,等待被执行,其中VM_GenCollectForAllocation的doit方法实现:

通过VMThread调度执行gc操作,最终调用对应的doit方法:

1、利用SvcGCMarker通知minor gc操作的开始;

2、设置触发gc的原因为GCCause::_allocation_failure,即内存分配失败;

3、其中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操作必须满足四个条件:

1、在一个同步安全点,VMThread在调用gc操作时会通过SafepointSynchronize::begin/end方法实现进出安全区域,调用begin方法时会强制所有线程到达一个安全点;

2、当前线程是VM线程或并发的gc线程;

3、当前线程已经获得内存堆的全局锁;

4、内存堆当前_is_gc_active参数为false,即还未开始gc;

如果当前有其它线程触发了gc,则终止当前的gc线程,否则继续。

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

1、设置参数_is_gc_active为真,表示当前线程正式开始gc操作;

2、判断当前是否要进行一次full gc,并确定触发full gc的原因,如通过调用System.gc()触发;

3、如果设置了PrintGC和PrintGCDateStamps,则在输出日志中添加时间戳;

4、如果设置了PrintGCDetails,则打印本次gc的详细CPU耗时,如 user_time、system_time和real_time;

5、gc_prologue方法在gc开始前做一些前置处理,如设置每个内存代的_soft_end字段;

6、更新发生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的前置处理。

1、如果设置了参数HeapDumpBeforeFullGC,则对内存堆进行dump;

2、如果设置了参数PrintClassHistogramBeforeFullGC,则打印在进行FGC之前的对象;

1、统计各个内存代进行gc时的数据;

2、如果开启了ZapUnusedHeapArea,则在回收每个内存代时都要对内存代的内存上限地址top进行更新;

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

1、如果当前是FGC,则调用post_full_gc_dump方法通知gc已经完成,可以进行后续操作,如果设置了参数HeapDumpAfterFullGC,则在gc后可以对堆内存进行dump;如果设置了参数PrintClassHistogramAfterFullGC,则在gc后可以打印存活的对象;

2、如果设置了参数PrintGCDetails,则在gc后可以打印内存堆的变化情况;如果当前还是FGC,则还可以打印永久代的内存变化情况。

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

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

时间: 2024-10-28 16:33:41

深入理解Java之垃圾回收的相关文章

全面解析Java的垃圾回收机制

转自:http://www.cnblogs.com/laoyangHJ/archive/2011/08/17/JavaGC.html —————————————————————————————————— Java的堆是一个运行时数据区,类的实例(对象)从中分配空间.Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,这些对象通过new.newarray.anewarray和multianewarray等指令建立,但是它们不需要程序代码来显式地释放.一般来说,堆的是由垃圾回收来

Java的垃圾回收机制

以前很少关注内存的问题,基本没有关注,这方面的小白,原因在于自己都是写的自我娱乐的小程序,不关注性能,不是提供服务.而企业级别的应用在程序稳健性方面的要求大大提高,因此要考虑更多的问题.对于大公司来说,为了应对各种情况,服务器资源肯定充足,但是由于应用很多,那么我们要尽可能的节省资源,对于Java程序,内存资源相当宝贵,那么理解java里面怎么做垃圾回收,是很重要的. 我阅读的参考主要是两个内容[1,2],但还没有做具体的实践,也没有切身体会,只能纸上谈兵了. 那么首先要介绍Java里面的内存分

Java的垃圾回收之算法

引言 Java的堆是一个运行时数据区,类的实例(对象)从中分配空间.Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象, 这些对象通过new.newarray.anewarray和multianewarray等指令建立,但是它们不需要程序代码来显式地释放.一般来说,堆的是由 垃圾回收来负责的,尽管JVM规范并不要求特殊的垃圾回收技术,甚至根本就不需要垃圾回收,但是由于内存的有限性,JVM在实现的时 候都有一个由垃圾回收所管理的堆.垃圾回收是一种动态存储管理技术,它自动地释放不

浅析JAVA的垃圾回收机制(GC)

1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制. 注意:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身.换言之,垃圾回收只会负责释放那些对象占有的内存.对象是个抽象的词,包括引用和其占据的内存空间.当对象没有任何引用时其占据的内存空间随即被收回备用,此时对象也就被销毁.但不能说是回收对象,可以理解为一种文字游戏. 分析: 引用:如果Referen

面试官,不要再问我“Java GC垃圾回收机制”了

Java GC垃圾回收几乎是面试必问的JVM问题之一,本篇文章带领大家了解Java GC的底层原理,图文并茂,突破学习及面试瓶颈. 楔子-JVM内存结构补充 在上篇<JVM之内存结构详解>中有些内容我们没有讲,本篇结合垃圾回收机制来一起学习.还记得JVM中堆的结构图吗? 图中展示了堆中三个区域:Eden.From Survivor.To Survivor.从图中可以也可以看到它们的大小比例,准确来说是:8:1:1.为什么要这样设计呢,本篇文章后续会给出解答,还是根据垃圾回收的具体情况来设计的.

Java的垃圾回收机制笔记

Java的垃圾回收机制笔记 java垃圾回收的意义 确保不再被引用的对象的内存空间被回收. 确保被引用的对象的内存不被错误回收. 再分配内存. java垃圾回收的常用方法 引用计数收集器 堆中的每个对象(不是对象的引用)都有一个引用计数.当一个对象被创建时,给该对象分配一个变量,该变量计数设置设置为1.当任何其他变量被赋值为这个对象的引用,计数加1(a=b,则b引用的对象计数+1),但当一个对象的某个引用超过生命周期或者被设置为一个新值的时候,引用的计数减1(a=c,则a不再指向b指向的对象,而

java的垃圾回收

此文为转载,但是笔者忘记转载自哪里,望原作者看见之后不要怪罪 1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的一个系统级线程会自动释放该内存块.垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃.当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用.事实上,除了释放没用的对象,垃圾回收也可以清除内存

Java 的垃圾回收机制(转)

先看一段转载,原文出自 http://jefferent.iteye.com/blog/1123677 虚拟机中的共划分为三个代:年轻代(Young Generation).年老点(Old Generation)和持久代(Permanent Generation).其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大.年轻代和年老代的划分是对垃圾收集影响比较大的. 年轻代: 所有新生成的对象首先都是放在年轻代的.年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象.

java的垃圾回收机制的特点

浅谈java的垃圾回收机制的特点: 1.垃圾回收机制的目标是回收无用对象的内存空间(记住:不是对象),这些内存空间是JVM堆内存的内存空间.垃圾回收只回收内存资源,对于那些物理资源,如数据库连接,Socket,I/O流等资源无能无能为力,我们要自己关闭回收. 2.为了加快垃圾回收机制回收那些无用对象所占的内存空间,我们可以讲对象的引用变量置于null(记住:置于null后,垃圾回收机制不会立即执行的). 3.垃圾回收机制的潜在缺点它的开销会影响性能.Java虚拟机必须跟踪程序中有用的对象才可以确