今天从实战角度分析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
的源码体系非常大,如果想学源码的话,可以参考今天这个 场景,从场景切入(找几个简单的场景),读源码要有目的,这样才能做到有的放矢,才能真正的提高自己的技术水平。