跳到主要内容

12、JVM 调优实战 - 实战角度分析JVM 源码

今天从实战角度分析JVM源码,首先:

1、使用 SourceInsight 来查看 OpenJDK 源代码

如何查看可以见一下文档。工具使用的 SourceInsight

https://cloud.tencent.com/developer/article/1585224

 

这个会耗时久点,但是可以关联全部的。

2、应用分析场景、堆外内存默认是多大

如果我们没有通过 -XX:MaxDirectMemorySize 来指定最大的堆外内存,那么默认的最大堆外内存是多少呢?

一般来说,如果没有显示的设置 -XX:MaxDirectMemorySize 参数,通过 ByteBuffer 能够分配的直接内存空间大小就是堆的最大大小。

对应参数-Xmx,真的是这么样吗?

 

 

案例分析:

 

1、 VM参数配置:-XX:MaxDirectMemorySize=100m;

 

 

2、 VM参数配置:-XX:MaxDirectMemorySize=128m;

 

3、 VM参数配置:-Xmx128m;

 

这个地方居然报错了,果然童话里都是骗人的。

4、 VM参数配置:-Xmx135m-Xmn100m-XX:SurvivorRatio=8;

 

5、 VM参数配置:-Xmx138m-Xmn100m-XX:SurvivorRatio=8;

 

 

几个案例分析,我们得出以下结论:

没有显示的设置-XX:MaxDirectMemorySize 参数,通过 ByteBuffer 能够分配的直接内存空间大小就是堆的最大大小。 这句话是有问题的。 问题在哪里,应该是没有显示的设置-XX:MaxDirectMemorySize 参数,通过 ByteBuffer 能够分配的直接内存空间大小就是堆的最大的 可使用 的大小。

堆的最大的 可使用 的大小= 堆的最大值- 一个 Survivor 的大小(浪费的空间),所以案例 4 为什么会 OOM!

堆的最大的可使用的大小=135-10m=125m ,不能分配 128M 的对象

所以案例 5 为什么不会 OOM! 堆的最大的可使用的大小=138-10m=128m ,刚好可以分配 128M 的对象

6、源码分析

我们从代码出发,依次来找

 

 

 

 

 

 

7、 看到上面的代码之后不要误以为默认的最大值是64M?其实不是的;

说到这个值得从 java.lang.System 这个类的初始化说起 上 面 这 个 方 法 在 jvm 启 动 的 时 候 对 System 这 个 类 做 初 始 化 的 时 候 执 行 的 , 因 此 执 行 时 间 非 常 早 , 我 们 看 到 里 面 调 用 了

sun.misc.VM.saveAndRemoveProperties(props):

 

 

 

 

8、 这个地方是一个native方法,这个是一个本地方法,本地方法里面怎么实现的这个地方就需要看JVM的源码;

像这种本地方法,在 VM 的源码中一般都是会把包名加上,因为是给 java 用的所以前缀上还有一个 java。

大致推算出(其实已经知道了)是 JVM 的这个函数 : Java_java_lang_Runtime_maxMemory

 

 

 

9、 这个容量其实就是每一个代的大小相加,比如YGen+OldGen之类;

 

 

10、 在这里可以看到,新生代的最大值=新生代的最大值-一个survivor的大小

为什么会这样,因为在新生代采用复制回收算法,一个幸存者区域是浪费的,所以实际空间最大大小要减去一个交换器的大小。

而老年代是没有空间浪费的,所以还是全区域。 也得出我们设置的-Xmx 的值里减去一个 survivor 的大小就是默认的堆外内存的大小。

11、总结

读JVM 的源码确实可以解决不少问题,但读 JVM 的源码门槛很高( C++ 的基础),同时 JVM 的源码体系非常大,如果想学源码的话,可以参考今天这个 场景,从场景切入(找几个简单的场景),读源码要有目的,这样才能做到有的放矢,才能真正的提高自己的技术水平。