Java虚拟机System.gc()解析

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 对于Java语言来说是不用刻意手动去释放内存,同时,也尽可能不需要手动去干预Java虚拟机的GC行为。在本篇文章中,我们试图从多个方面去解析有关System.gc()API调用的最常见问题。希望对需要了解这块技术的朋友有所帮助。

      对于Java语言来说是不用刻意手动去释放内存,同时,也尽可能不需要手动去干预Java虚拟机的GC行为。在本篇文章中,我们试图从多个方面去解析有关System.gc()API调用的最常见问题。希望对需要了解这块技术的朋友有所帮助。    

System.gc()是干什么的?

      System.gc()是Java,Android,Go、C#以及其他流行语言所提供的API。当调用时,它将尽最大努力从内存中清除累积的未引用对象(即我们常说的垃圾回收)。Hotspot为我们开放了Java语言级别的GC手动触发入口System.gc(),我们可以看下其源码,首先,我们了解下 java.lang.System#gc()方法,具体下所示:


/**
     * Runs the garbage collector.
     * <p>
     * Calling the <code>gc</code> method suggests that the Java Virtual
     * Machine expend effort toward recycling unused objects in order to
     * make the memory they currently occupy available for quick reuse.
     * When control returns from the method call, the Java Virtual
     * Machine has made a best effort to reclaim space from all discarded
     * objects.
     * <p>
     * The call <code>System.gc()</code> is effectively equivalent to the
     * call:
     * <blockquote><pre>
     * Runtime.getRuntime().gc()
     * </pre></blockquote>
     *
     * @see     java.lang.Runtime#gc()
     */
    public static void gc() {
        Runtime.getRuntime().gc();
    }

       根据上述源码,我们追溯到java.lang.Runtime#gc()方法,具体如下所示:


/**
     * Runs the garbage collector.
     * Calling this method suggests that the Java virtual machine expend
     * effort toward recycling unused objects in order to make the memory
     * they currently occupy available for quick reuse. When control
     * returns from the method call, the virtual machine has made
     * its best effort to recycle all discarded objects.
     * <p>
     * The name <code>gc</code> stands for "garbage
     * collector". The virtual machine performs this recycling
     * process automatically as needed, in a separate thread, even if the
     * <code>gc</code> method is not invoked explicitly.
     * <p>
     * The method {@link System#gc()} is the conventional and convenient
     * means of invoking this method.
     */
    public native void gc();


     有关Runtime.c # Java_java_lang_Runtime_gc()方法调用了native方法gc(),对应的方法

在Hotspot源码:src/java.base/share/native/libjava/Runtime.c,具体如下所示:


JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
    JVM_GC();
}


      有关jvm.cpp方法实现在src/hotspot/share/prims/jvm.cpp,具体如下所示:


JVM_ENTRY_NO_ENV(void, JVM_GC(void))
  JVMWrapper("JVM_GC");
  if (!DisableExplicitGC) {
     // 调用具体堆实现的collect方法
    Universe::heap()->collect(GCCause::_java_lang_system_gc);
  }
JVM_END

       通过Universe调用具体堆实现的collect方法,取决于使用当前实例使用的GC模式,在JVM中目前堆实现主要有:


- 串行回收堆实现 - src/hotspot/share/gc/serial/defNewGeneration.cpp(年轻代)
               - src/hotspot/share/gc/serial/tenuredGeneration.cpp(年老代) 
- 并行回收堆实现 - src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp 
- CMS并发回收堆实现 - src/hotspot/share/gc/shared/genCollectedHeap.cpp 
- G1并发回收堆实现 - src/hotspot/share/gc/g1/g1CollectedHeap.cpp       


      我们可以通过分析应用程序堆栈信息,探索调用System.gc()的相关可疑线索,通常主要有以下几种场景:

       1、应用程序能正在显式调用System.gc()方法。

       2、由应用程序触发的第三方库,框架甚至其他底层组件调用System.gc()方法。

       3、使用JMX从外部工具(如VisualVM)触发。

       4、若应用程序或其调用的框架正在使用RMI,则RMI会定期调用System.gc()。

调用System.gc()有什么弊端?

     当我们的应用程序调用(显/隐)System.gc()或 Runtime.getRuntime().gc() API时,Stop-the-World Full GC事件将被触发。在Stop-the-World Full GC期间中,整个JVM将会挂起(即,所有正在运行的业务将被临时暂停)。通常,这些Full GC操作需要很长时间才能完成。因此,在不需要运行GC的场景下尽可能不要去触发此种不必要的操作,毕竟,触发System.gc()有可能导致业务中断以及不良的用户体验,遭到客户的投诉。

      Java虚拟机本身具有复杂的算法,该算法跟随者我们的应用逻辑处理默默在后台运行,进行着所有计算以及有关何时触发GC的计算。当我们程序调用System.gc()时,所有这些计算都将投入使用。如果JVM仅在一毫秒后触发了GC事件,然后又从应用程序中再次调用System.gc(),该怎么办?因为从我们的应用程序压根就不清楚GC何时运行。

什么场景下调用System.gc()?

      目前,无论是基于早期的JDK 1.4版本还是现在的JDK 15,不建议、也不推荐应用程序调用System.gc()方法。但是,可能在某种特定的场景下,也可尝试调用此方法达到应用服务性能最大化。

      以下为某一家大型航空公司实际案例:该应用程序使用1 TB的内存。此应用程序的完整GC暂停时间大约需要5分钟才能完成。是的,它是5分钟(但我们也看到了GC暂停时间为23分钟的情况)。为了避免由于此暂停时间而对客户造成的影响,该航空公司已实施了明智的解决方案。每天晚上,他们一次从负载均衡器池中取出一个JVM实例。然后,它们通过该JVM上的JMX显式触发System.gc()调用。一旦GC事件完成并且从内存中清除了垃圾,他们就会将该JVM放回到负载平衡器池中。通过这种巧妙的解决方案,他们将这5分钟的GC暂停时间对客户的影响降到最低。

如何判断应用程序进行了System.gc()调用?

      正如上面的在“谁调用System.gc()?”部分中所列述的场景,我们可以看到System.gc()调用是从多个源进行的,而不仅仅是从我们的应用程序源代码进行的。因此,搜索应用程序代码“ System.gc()”字符串不足以判断我们的应用程序是否在进行System.gc()调用。因此,这就面临一个问题:如何检测是否在整个应用程序堆栈中调用了System.gc()调用?

      当然有,在应用程序中启用GC日志。实际上,建议在生产环境中所有的服务器中尽可能都启用GC日志标识,因为它有助于我们排除故障并优化应用程序性能。启用GC日志会增加微不足道的开销(如果可以观察到的话)。同时,我们也可以将收集的GC日志通过垃圾收集日志分析器工具进行分析,从而可生成丰富的垃圾收集分析报告,如下图:


如何禁用System.gc()调用?

      我们可以通过以下解决方案去除显式的System.gc()调用:

      1、搜索和替换

     这可能是一种传统方法,但是可以实现一部分场景。在应用程序代码库中搜索“ System.gc()”和“ Runtime.getRuntime().gc()”。如果看到匹配项,则将其移除。如果从我们的应用程序源代码中调用“ System.gc()”,则此解决方案将起作用。如果要从我们的第三方库,框架或通过外部源调用“ System.gc()”,则此解决方案将无法使用。在这种情况下,我们可以考虑使用#2中概述的选项。

       2、-XX: + DisableExplicitGC参数

      启动应用程序时,可以通过配置JVM参数“ -XX:+ DisableExplicitGC”来强制禁用System.gc()调用。此选项将使在应用程序堆栈中任何位置调用的所有“ System.gc()”调用静音。

       3、-XX: + ExplicitGCInvokesConcurrent参数

      我们可以配置“ -XX:+ ExplicitGCInvokesConcurrent” JVM参数。传递此参数后,GC集合将与应用程序线程同时运行,以减少冗长的暂停时间。

       4、 RMI

      如果我们的应用程序使用RMI,则可以控制“ System.gc()”调用的频率。启动应用程序时,可以使用以下JVM参数配置该频率:


-Dsun.rmi.dgc.server.gcInterval = n
-Dsun.rmi.dgc.client.gcInterval = n


       这些属性的默认值在:

       JDK 1.4.2和5.0为60000毫秒(即60秒)

       JDK 6及更高版本为3600000毫秒(即60分钟)

       同时,因业务场景差异化,我们可能需要将这些属性设置为非常高的值,以便可以将影响最小化。


相关文章
|
5天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
40 6
|
12天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
3天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java的集合框架中,Set接口以其“无重复”特性著称。本文解析了Set的实现原理,包括HashSet和TreeSet的不同数据结构和算法,以及如何通过示例代码实现最佳实践。选择合适的Set实现类和正确实现自定义对象的hashCode()和equals()方法是关键。
12 4
|
1天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
9 2
|
6天前
|
Java 编译器 数据库连接
Java中的异常处理机制深度解析####
本文深入探讨了Java编程语言中异常处理机制的核心原理、类型及其最佳实践,旨在帮助开发者更好地理解和应用这一关键特性。通过实例分析,揭示了try-catch-finally结构的重要性,以及如何利用自定义异常提升代码的健壮性和可读性。文章还讨论了异常处理在大型项目中的最佳实践,为提高软件质量提供指导。 ####
|
10天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
9天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
27 2
|
9天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
14天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
30 1
|
9天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
10 0

推荐镜像

更多