Java虚拟机System.gc()解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 对于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分钟)

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


相关文章
|
8天前
|
存储 Java 开发者
浅析JVM方法解析、创建和链接
上一篇文章《你知道Java类是如何被加载的吗?》分析了HotSpot是如何加载Java类的,本文再来分析下Hotspot又是如何解析、创建和链接类方法的。
|
20天前
|
Java 编译器
Java 泛型详细解析
本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。
30 2
Java 泛型详细解析
|
20天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12
|
17天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
17天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
23天前
|
数据采集 存储 Web App开发
Java爬虫:深入解析商品详情的利器
在数字化时代,信息处理能力成为企业竞争的关键。本文探讨如何利用Java编写高效、准确的商品详情爬虫,涵盖爬虫技术概述、Java爬虫优势、开发步骤、法律法规遵守及数据处理分析等内容,助力电商领域市场趋势把握与决策支持。
|
22天前
|
Java 编译器 API
深入解析:JDK与JVM的区别及联系
在Java开发和运行环境中,JDK(Java Development Kit)和JVM(Java Virtual Machine)是两个核心概念,它们在Java程序的开发、编译和运行过程中扮演着不同的角色。本文将深入解析JDK与JVM的区别及其内在联系,为Java开发者提供清晰的技术干货。
23 1
|
22天前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####
|
6天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
36 6

推荐镜像

更多
下一篇
DataWorks