JVM源码分析之SystemGC完全解读

2024-09-05 12:18

本文主要是介绍JVM源码分析之SystemGC完全解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可以通过jmap来触发等,针对每个场景其实我们都可以写篇文章来做一个介绍,本文重点介绍下System.gc的原理

或许大家已经知道如下相关的知识

  • system.gc其实是做一次full gc
  • system.gc会暂停整个进程
  • system.gc一般情况下我们要禁掉,使用-XX:+DisableExplicitGC
  • system.gc在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent来做一次稍微高效点的GC(效果比Full GC要好些)
  • system.gc最常见的场景是RMI/NIO下的堆外内存分配等

如果你已经知道上面这些了其实也说明你对System.gc有过一定的了解,至少踩过一些坑,但是你是否更深层次地了解过它,比如

  • 为什么CMS GC下-XX:+ExplicitGCInvokesConcurrent这个参数加了之后会比真正的Full GC好?
  • 它如何做到暂停整个进程?
  • 堆外内存分配为什么有时候要配合System.gc?

如果你上面这些疑惑也都知道,那说明你很懂System.gc了,那么接下来的文字你可以不用看啦

JDK里的System.gc的实现

先贴段代码吧(java.lang.System)

    /*** 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();}

发现主要调用的是Runtime里的gc方法(java.lang.Runtime)

    /*** 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();

这里看到gc方法是native的,在java层面只能到此结束了,代码只有这么多,要了解更多,可以看方法上面的注释,不过我们需要更深层次地来了解其实现,那还是准备好进入到jvm里去看看

Hotspot里System.gc的实现

如何找到native里的实现

上面提到了Runtime.gc是一个本地方法,那需要先在jvm里找到对应的实现,这里稍微提一下jvm里native方法最常见的也是最简单的查找,jdk里一般含有native方法的类,一般都会有一个对应的c文件,比如上面的java.lang.Runtime这个类,会有一个Runtime.c的文件和它对应,native方法的具体实现都在里面了,如果你有source,可能会猜到和下面的方法对应

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

其实没错的,就是这个方法,jvm要查找到这个native方法其实很简单的,看方法名可能也猜到规则了,Java_pkgName_className_methodName,其中pkgName里的"."替换成"_",这样就能找到了,当然规则不仅仅只有这么一个,还有其他的,这里不细说了,有机会写篇文章详细介绍下其中细节

DisableExplicitGC参数

上面的方法里是调用JVM_GC(),实现如下

JVM_ENTRY_NO_ENV(void, JVM_GC(void))JVMWrapper("JVM_GC");if (!DisableExplicitGC) {Universe::heap()->collect(GCCause::_java_lang_system_gc);}
JVM_END

看到这里我们已经解释其中一个疑惑了,就是DisableExplicitGC这个参数是在哪里生效的,起的什么作用,如果这个参数设置为true的话,那么将直接跳过下面的逻辑,我们通过-XX:+ DisableExplicitGC就是将这个属性设置为true,而这个属性默认情况下是true还是false呢

product(bool, DisableExplicitGC, false,                                   \"Tells whether calling System.gc() does a full GC")    

ExplicitGCInvokesConcurrent参数

这里主要针对CMSGC下来做分析,所以我们上面看到调用了heap的collect方法,我们找到对应的逻辑

void GenCollectedHeap::collect(GCCause::Cause cause) {if (should_do_concurrent_full_gc(cause)) {
#ifndef SERIALGC// mostly concurrent full collectioncollect_mostly_concurrent(cause);
#else  // SERIALGCShouldNotReachHere();
#endif // SERIALGC} else {
#ifdef ASSERTif (cause == GCCause::_scavenge_alot) {// minor collection onlycollect(cause, 0);} else {// Stop-the-world full collectioncollect(cause, n_gens() - 1);}
#else// Stop-the-world full collectioncollect(cause, n_gens() - 1);
#endif}
}bool GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {return UseConcMarkSweepGC &&((cause == GCCause::_gc_locker && GCLockerInvokesConcurrent) ||(cause == GCCause::_java_lang_system_gc && ExplicitGCInvokesConcurrent));
}

collect里一开头就有个判断,如果should_do_concurrent_full_gc返回true,那会执行collect_mostly_concurrent做并行的回收

其中should_do_concurrent_full_gc中的逻辑是如果使用CMS GC,并且是system gc且ExplicitGCInvokesConcurrent==true,那就做并行full gc,当我们设置-XX:+ ExplicitGCInvokesConcurrent的时候,就意味着应该做并行Full GC了,不过要注意千万不要设置-XX:+DisableExplicitGC,不然走不到这个逻辑里来了

并行Full GC相对正常的Full GC效率高在哪里

stop the world

说到GC,这里要先提到VMThread,在jvm里有这么一个线程不断轮询它的队列,这个队列里主要是存一些VM_operation的动作,比如最常见的就是内存分配失败要求做GC操作的请求等,在对gc这些操作执行的时候会先将其他业务线程都进入到安全点,也就是这些线程从此不再执行任何字节码指令,只有当出了安全点的时候才让他们继续执行原来的指令,因此这其实就是我们说的stop the world(STW),整个进程相当于静止了

CMS GC

这里必须提到CMS GC,因为这是解释并行Full GC和正常Full GC的关键所在,CMS GC我们分为两种模式background和foreground,其中background顾名思义是在后台做的,也就是可以不影响正常的业务线程跑,触发条件比如说old的内存占比超过多少的时候就可能触发一次background式的cms gc,这个过程会经历CMS GC的所有阶段,该暂停的暂停,该并行的并行,效率相对来说还比较高,毕竟有和业务线程并行的gc阶段;而foreground则不然,它发生的场景比如业务线程请求分配内存,但是内存不够了,于是可能触发一次cms gc,这个过程就必须是要等内存分配到了线程才能继续往下面走的,因此整个过程必须是STW的,因此CMS GC整个过程都是暂停应用的,但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,这些省下来的阶段主要是并行阶段,Precleaning、AbortablePreclean,Resizing这几个阶段都不会经历,其中sweep阶段是同步的,但不管怎么说如果走了类似foreground的cms gc,那么整个过程业务线程都是不可用的,效率会影响挺大。CMS GC具体的过程后面再写文章详细说,其过程确实非常复杂的

正常的Full GC

正常的Full GC其实是整个gc过程包括ygc和cms gc(这里说的是真正意义上的Full GC,还有些场景虽然调用Full GC的接口,但是并不会都做,有些时候只做ygc,有些时候只做cms gc)都是由VMThread来执行的,因此整个时间是ygc+cms gc的时间之和,其中CMS GC是上面提到的foreground式的,因此整个过程会比较长,也是我们要避免的

并行的Full GC

并行Full GC也通样会做YGC和CMS GC,但是效率高就搞在CMS GC是走的background的,整个暂停的过程主要是YGC+CMS_initMark+CMS_remark几个阶段

堆外内存常配合使用System GC

这里说的堆外内存主要针对java.nio.DirectByteBuffer,这些对象的创建过程会通过Unsafe接口直接通过os::malloc来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存,具体堆外内存是如何回收的,其原理机制又是怎样的,还是后面写篇详细的文章吧

该文章来自阿里巴巴技术协会( ATA )精选集

这篇关于JVM源码分析之SystemGC完全解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1138938

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("