JVM 的 GC 一般情况下是 JVM 本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过 jvmti 做强制 GC,通过 System.gc 触发,还可以通过 jmap 来触发等,针对每个场景其实我们都可以写篇文章来做一个介绍,本文重点介绍下 System.gc 的原理
或许大家已经知道如下相关的知识
如果你已经知道上面这些了其实也说明你对 System.gc 有过一定的了解,至少踩过一些坑,但是你是否更深层次地了解过它,比如
如果你上面这些疑惑也都知道,那说明你很懂 System.gc 了,那么接下来的文字你可以不用看啦
先贴段代码吧(java.lang.System)
|
|
发现主要调用的是 Runtime 里的 gc 方法(java.lang.Runtime)
|
|
这里看到 gc 方法是 native 的,在 java 层面只能到此结束了,代码只有这么多,要了解更多,可以看方法上面的注释,不过我们需要更深层次地来了解其实现,那还是准备好进入到 jvm 里去看看
上面提到了 Runtime.gc 是一个本地方法,那需要先在 jvm 里找到对应的实现,这里稍微提一下 jvm 里 native 方法最常见的也是最简单的查找,jdk 里一般含有 native 方法的类,一般都会有一个对应的 c 文件,比如上面的 java.lang.Runtime 这个类,会有一个 Runtime.c 的文件和它对应,native 方法的具体实现都在里面了,如果你有 source,可能会猜到和下面的方法对应
|
|
其实没错的,就是这个方法,jvm 要查找到这个 native 方法其实很简单的,看方法名可能也猜到规则了,Java_pkgName_className_methodName,其中 pkgName 里的 "." 替换成 "_",这样就能找到了,当然规则不仅仅只有这么一个,还有其他的,这里不细说了,有机会写篇文章详细介绍下其中细节
上面的方法里是调用 JVM_GC(),实现如下
|
|
看到这里我们已经解释其中一个疑惑了,就是
这个参数是在哪里生效的,起的什么作用,如果这个参数设置为 true 的话,那么将直接跳过下面的逻辑,我们通过 - XX:+ DisableExplicitGC 就是将这个属性设置为 true,而这个属性默认情况下是 true 还是 false 呢
- DisableExplicitGC
|
|
这里主要针对 CMSGC 下来做分析,所以我们上面看到调用了 heap 的 collect 方法,我们找到对应的逻辑
|
|
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,不然走不到这个逻辑里来了
说到 GC,这里要先提到 VMThread,在 jvm 里有这么一个线程不断轮询它的队列,这个队列里主要是存一些 VM_operation 的动作,比如最常见的就是内存分配失败要求做 GC 操作的请求等,在对 gc 这些操作执行的时候会先将其他业务线程都进入到安全点,也就是这些线程从此不再执行任何字节码指令,只有当出了安全点的时候才让他们继续执行原来的指令,因此这其实就是我们说的 stop the world(STW),整个进程相当于静止了
这里必须提到 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 其实是整个 gc 过程包括 ygc 和 cms gc(这里说的是真正意义上的 Full GC,还有些场景虽然调用 Full GC 的接口,但是并不会都做,有些时候只做 ygc,有些时候只做 cms gc) 都是由 VMThread 来执行的,因此整个时间是 ygc+cms gc 的时间之和,其中 CMS GC 是上面提到的 foreground 式的,因此整个过程会比较长,也是我们要避免的
并行 Full GC 也通样会做 YGC 和 CMS GC,但是效率高就搞在 CMS GC 是走的 background 的,整个暂停的过程主要是 YGC+CMS_initMark+CMS_remark 几个阶段
这里说的堆外内存主要针对 java.nio.DirectByteBuffer,这些对象的创建过程会通过 Unsafe 接口直接通过 os::malloc 来分配内存,然后将内存的起始地址和大小存到 java.nio.DirectByteBuffer 对象里,这样就可以直接操作这些内存。这些内存只有在 DirectByteBuffer 回收掉之后才有机会被回收,因此如果这些对象大部分都移到了 old,但是一直没有触发 CMS GC 或者 Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过 - XX:MaxDirectMemorySize 来指定最大的堆外内存大小,当使用达到了阈值的时候将调用 System.gc 来做一次 full gc,以此来回收掉没有被使用的堆外内存,具体堆外内存是如何回收的,其原理机制又是怎样的,还是后面写篇详细的文章吧
来源: http://www.bubuko.com/infodetail-1973556.html