深入理解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进程。

目录
相关文章
|
16天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
28天前
|
监控 算法 Java
深入理解Java的垃圾回收机制
【10月更文挑战第22天】在Java的世界里,有一个默默无闻却至关重要的角色——垃圾回收(Garbage Collection, GC)。就像城市的清洁工一样,它默默地清理着不再使用的内存空间,确保我们的程序运行得既高效又稳定。但你真的了解垃圾回收是如何工作的吗?让我们一起探索这个看似简单却充满奥秘的过程,看看它是如何影响你的Java应用性能的。
|
1月前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。
|
1月前
|
算法 Java 开发者
Java中的垃圾回收机制:从原理到实践
Java的垃圾回收机制(Garbage Collection, GC)是其语言设计中的一大亮点,它为开发者提供了自动内存管理的功能,大大减少了内存泄漏和指针错误等问题。本文将深入探讨Java GC的工作原理、不同垃圾收集器的种类及它们各自的优缺点,并结合实际案例展示如何调优Java应用的垃圾回收性能,旨在帮助读者更好地理解和有效利用Java的这一特性。
|
1月前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制
【10月更文挑战第2天】 在本文中,我们将深入探讨Java编程语言中的内存管理机制,特别是垃圾回收机制。我们将从基本原理、垃圾回收算法到实际应用场景全面解析,帮助你更好地理解和优化Java应用的内存使用。无论你是初学者还是有经验的开发者,这篇文章都能带给你新的启发和思考。
32 2
|
1月前
|
算法 Java 程序员
深入理解Java的垃圾回收机制
【9月更文挑战第31天】在Java的世界里,有一个默默守护者,它负责清理不再使用的对象,确保内存的有效利用。这就是垃圾回收器(Garbage Collector, GC)。本文将带你一探究竟,了解它是如何工作的,以及为何我们需要关心它的存在。
|
2月前
|
监控 Java 程序员
深入理解Java中的垃圾回收机制
【9月更文挑战第29天】在Java编程的海洋中,垃圾回收(Garbage Collection, GC)是维持内存健康的灯塔。本文将带你探索GC的奥秘,从它的本质、工作机制到优化策略,让你的代码像海豚一样优雅地畅游。
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
117 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
1月前
|
存储 监控 算法
深入理解Java内存模型与垃圾回收机制
【10月更文挑战第10天】深入理解Java内存模型与垃圾回收机制
21 0
|
1月前
|
监控 算法 Java
Java中的垃圾回收机制深度解析
【10月更文挑战第10天】 本文深入探讨了Java语言核心特性之一的垃圾回收机制(Garbage Collection, GC),揭示了其在内存管理中的关键角色。通过对GC的工作原理、分类、算法以及调优策略的细致分析,旨在帮助开发者更好地理解并有效利用这一机制,提升Java应用的性能与可靠性。不同于常规摘要,本文聚焦于为读者提供一份关于Java GC全面而深入的解读,助力把握Java内存管理的精髓。
下一篇
无影云桌面