深入理解Java虚拟机(三)
深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版)
#自动内存管理
#Java 内存区域与内存溢出异常
Java 虚拟机运行时数据区
#虚拟机栈和本地方法栈溢出
由于 HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,因此对于 HotSpot 来说,-Xoss
参数(设置本地方法栈大小)虽然存在,但实际上是没有任何效果的,栈容量只能由-Xss
参数来设定。
关于虚拟机栈和本地方法栈,在《Java 虚拟机规范》中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出
StackOverflowError
异常。 - 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出
OutOfMemoryError
异常。
Java 虚拟机规范》明确允许 Java 虚拟机实现自行选择是否支持栈的动态扩展,而 HotSpot 虚拟机的选择是不支持扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现OutOfMemoryError
异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError
异常。
1 | package xyz.onns.jvm; |
1 | "D:\Program Files\java\jdk-13.0.1\bin\java.exe" -Xss128k "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\lib\idea_rt.jar=57372:D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\bin" -Dfile.encoding=UTF-8 -classpath D:\weiyun\Code\java\jvm\out\production\jvm xyz.onns.jvm.JavaVMStackSOF |
修改参数为-Xss180k
。
1 | "D:\Program Files\java\jdk-13.0.1\bin\java.exe" -Xss180k "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\lib\idea_rt.jar=57575:D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\bin" -Dfile.encoding=UTF-8 -classpath D:\weiyun\Code\java\jvm\out\production\jvm xyz.onns.jvm.JavaVMStackSOF |
1 | package xyz.onns.jvm; |
1 | "D:\Program Files\java\jdk-13.0.1\bin\java.exe" -Xss180k "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\lib\idea_rt.jar=58065:D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\bin" -Dfile.encoding=UTF-8 -classpath D:\weiyun\Code\java\jvm\out\production\jvm xyz.onns.jvm.JavaVMStackSOF2 |
无论是由于栈帧太大还是虚拟机栈容量太小,当新的栈帧内存无法分配的时候,
HotSpot 虚拟机抛出的都是StackOverflowError
异常。
#方法区和运行时常量池溢出
String::intern()
是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此 String 对象的字符串,则返回代表池中这个字符串的 String 对象的引用;否则,会将此 String 对象包含的字符串添加到常量池中,并且返回此 String 对象的引用。
在 JDK 6 或更早之前的 HotSpot 虚拟机中,常量池都是分配在永久代中,我们可以通过-XX:PermSize
和-XX:MaxPermSize
限制永久代的大小,即可间接限制其中常量池的容量。
自 JDK 7 起,原本存放在永久代的字符串常量池被移至 Java 堆之中。
为了让使用者有预防实际应用里出现类似于“迫使虚拟机产生方法区的溢出异常”的破坏性的操作,HotSpot 还是提供了一些参数作为元空间的防御措施,主要包括:
-XX:MaxMetaspaceSize
:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。-XX:MetaspaceSize
:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize
(如果设置了的话)的情况下,适当提高该值。-XX:MinMetaspaceFreeRatio
:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio
,用于控制最大的元空间剩余容量的百分比。
#本机直接内存溢出
直接内存
(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize
参数来指定,如果不去指定,则默认与 Java 堆最大值(由-Xmx
指定)一致。
由直接内存导致的内存溢出,一个明显的特征是在Heap Dump
文件中不会看见有什么明显的异常情况,如果读者发现内存溢出之后产生的 Dump 文件很小,而程序中又直接或间接使用了 DirectMemory(典型的间接使用就是 NIO),那就可以考虑重点检查一下直接内存方面的原因了。
1 | package xyz.onns.jvm; |
很奇怪,在 IDEA 运行这段代码的时候我不会有异常抛出…我的 Java 版本是:
1 | $ java --version |