今天从实战角度分析JVM源码,首先:
1、使用
SourceInsight
来查看
OpenJDK
源代码
如何查看可以见一下文档。工具使用的
SourceInsight
https://cloud.tencent.com/developer/article/1585224
![](https://i-blog.csdnimg.cn/blog_migrate/3d7a05352fe47cb3779826a17b00f5f4.png)
这个会耗时久点,但是可以关联全部的。
2、应用分析场景、堆外内存默认是多大
如果我们没有通过
-XX:MaxDirectMemorySize
来指定最大的堆外内存,那么默认的最大堆外内存是多少呢?
一般来说,如果没有显示的设置
-XX:MaxDirectMemorySize
参数,通过
ByteBuffer
能够分配的直接内存空间大小就是堆的最大大小。
对应参数-Xmx,真的是这么样吗?
![](https://i-blog.csdnimg.cn/blog_migrate/6d090f4042ddc0f84a546674352c49aa.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5326996b7dc2772a9e7f6baebedcd3ff.png)
案例分析:
![](https://i-blog.csdnimg.cn/blog_migrate/1a486e71de3c148b62e0075eca2a3ef6.png)
1
、
VM
参数配置:
-XX:MaxDirectMemorySize=100m
![](https://i-blog.csdnimg.cn/blog_migrate/cf71569302d2811c2d03003805d2590c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/85f20fac3c8f391adbdbac26b8368ba0.png)
2、 VM 参数配置:-XX:MaxDirectMemorySize=128m
![](https://i-blog.csdnimg.cn/blog_migrate/962753f371f1f8b675d580cae11c63a3.png)
3、 VM 参数配置:-Xmx128m
![](https://i-blog.csdnimg.cn/blog_migrate/de3b6aa0da2f0432f01525d668f4d290.png)
这个地方居然报错了,果然童话里都是骗人的。
4、 VM 参数配置:-Xmx135m -Xmn100m -XX:SurvivorRatio=8
![](https://i-blog.csdnimg.cn/blog_migrate/0b6e785af7982f929335cbc350d3d379.png)
5、 VM 参数配置:-Xmx138m -Xmn100m -XX:SurvivorRatio=8
![](https://i-blog.csdnimg.cn/blog_migrate/ff6913d4c80ea1ec10e72b7c10b00ab7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/36bd73a58c3cbd0b98473b8e9856ffd6.png)
几个案例分析,我们得出以下结论:
没有显示的设置-XX:MaxDirectMemorySize 参数,通过 ByteBuffer 能够分配的直接内存空间大小就是堆的最大大小。 这句话是有问题的。 问题在哪里,应该是没有显示的设置-XX:MaxDirectMemorySize 参数,通过 ByteBuffer 能够分配的直接内存空间大小就是堆的最大的
可使用
的大小。
堆的最大的
可使用
的大小= 堆的最大值- 一个 Survivor 的大小(浪费的空间),所以案例 4 为什么会 OOM!
堆的最大的可使用的大小=135-10m=125m ,不能分配 128M 的对象
所以案例 5 为什么不会 OOM! 堆的最大的可使用的大小=138-10m=128m ,刚好可以分配 128M 的对象
6、源码分析
我们从代码出发,依次来找
![](https://i-blog.csdnimg.cn/blog_migrate/2eef895a0931f9954b490daec163498f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/49bbb881266562f349670070b825e90e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4fa7fa867d1f2a5ff949ce6f0de906a7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fdf927eb1b4cb7a338beee493c9a1985.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4e6863a45fbb88d2dba92b2d1e342044.png)
![](https://i-blog.csdnimg.cn/blog_migrate/92d223711d44f8c5180d696fc3503dbe.png)
7、看到上面的代码之后不要误以为默认的最大值是
64M
?其实不是的。
说到这个值得从
java.lang.System
这个类的初始化说起 上 面 这 个 方 法 在 jvm
启 动 的 时 候 对
System
这 个 类 做 初 始 化 的 时 候 执 行 的 , 因 此 执 行 时 间 非 常 早 , 我 们 看 到 里 面 调 用 了
sun.misc.VM.saveAndRemoveProperties(props):
![](https://i-blog.csdnimg.cn/blog_migrate/dd54778fe3cd1b84d53398e3dd1a25de.png)
![](https://i-blog.csdnimg.cn/blog_migrate/80daa8c115582c54f1618651c3c5c0ec.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f3c82db087d57a057bb632258ad24348.png)
![](https://i-blog.csdnimg.cn/blog_migrate/cfdfcc6e1e01980fc1b773ffa0e4a95b.png)
8、这个地方是一个 native 方法,这个是一个本地方法,本地方法里面怎么实现的。 这个地方就需要看 JVM 的源码。
像这种本地方法,在 VM 的源码中一般都是会把包名加上,因为是给 java 用的所以前缀上还有一个 java。
大致推算出(其实已经知道了)是 JVM 的这个函数 :
Java_java_lang_Runtime_maxMemory
![](https://i-blog.csdnimg.cn/blog_migrate/8a39458d7e5684d0a7c58188284b0b1d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/eb32c0056f4a20f22a702fc380532ca2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c7801799a37cf78a9b31c571cc1bb2b4.png)
9、这个容量其实就是每一个代的大小相加,比如 YGen+OldGen 之类
![](https://i-blog.csdnimg.cn/blog_migrate/4fde26025820ad5d6775abf474f07b9b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/bb72184b6e014b6843a534efe8bc8d29.png)
10、在这里可以看到,
新生代的最大值 = 新生代的最大值 - 一个 survivor 的大小。
为什么会这样,因为在新生代采用复制回收算法,一个幸存者区域是浪费的,所以实际空间最大大小要减去一个交换器的大小。
而老年代是没有空间浪费的,所以还是全区域。
也得出我们设置的-Xmx 的值里减去一个 survivor 的大小就是默认的堆外内存的大小。
11、总结
读
JVM
的源码确实可以解决不少问题,但读
JVM
的源码门槛很高(
C++
的基础),同时
JVM
的源码体系非常大,如果想学源码的话,可以参考今天这个 场景,从场景切入(找几个简单的场景),读源码要有目的,这样才能做到有的放矢,才能真正的提高自己的技术水平。