参考https://blog.csdn.net/weixin_42709563/article/details/106234230
《深入理解java虚拟机:jvm高级特性和最佳实践》
public class HeapOOM { static class OOMObject{ } public static void main(String[] args) { ArrayList<OOMObject> objects = new ArrayList<>(); while (true){ objects.add(new OOMObject()); } } }
错误信息提示“java.lang.OutOfMemoryError: Java heap space”
可以通过MAT进行分析定位
由于HotSpot不区分虚拟机栈和本地方法栈,所以栈容量只能由-Xss参数设置。虚拟机栈和本地方法栈在《规范》中有两种异常:(1)栈深度超限,抛出StackOverflowError;(2)如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。HotSpot虚拟机不支持扩展栈内存,所以除非 在创建线程申请内存时就因无法获得足够内存而出现OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。
每个线程的栈空间是线程独有的。对于HotSpot,-Xss参数表示单个线程的栈空间上限,如果-Xss较小,会导致没有空间创建新的栈帧,抛出StackOverflowError,如果在方法里定义大量变量,增加每个栈帧的大小,在相同的-Xss参数下,会导致能创建的栈帧数量变少,方法调用深度变少,最终也会抛出StackOverflowError。
对于多线程而言,每个线程都拥有-Xss参数大小的栈空间,如果反复循环创建线程。如果是32位的windows或者linux环境,每个进程可用的内存上限为若干GB,进程内存上限-堆内存-方法区内存-JVM自身内存-直接内存~=栈空间可用内存,当多个线程的栈空间和大于栈空间可用内存时,会抛出OutOfMemoryError异常。如果-Xss参数设置越大,会越快的出现这个异常。
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
不过对于64位的系统,进程内存上限达上百TB,不会出现上述的OutOfMemoryError异常,但是可能会不断侵占本地内存,导致系统异常。在64位linux服务器上尝试了下,cpu基本占满,内存达到53.8GB后增长缓慢,可能因为线程过多,主线程创建新线程的速度下降,内存增长放缓,但是cpu基本占满。
运行时常量池是方法区的一部分,但是jdk7以上将字符串常量池移到了java堆中。以下代码,如果在jdk6上运行,并且设置-XX: PermSize=6M -XX: MaxPermSize=6M时,会报OutOfMemoryError: PermGen space,即永久代(方法区)内存溢出;但是如果在jdk7及以上,字符串常量池移到了java堆中,由于堆内存十分大,下面代码的循环几乎一直运行,但是如果设置-Xmx为6MB,也会报OutOfMemoryError: Java heap space。
public class RuntimeConstantPoolOOM { public static void main(String[] args) { Set<String> set = new HashSet<>(); short i = 0; while (true){ System.out.println(i); set.add(String.valueOf(i).intern()); } } }
public class ConstantPool { public static void main(String[] args){ String str1 = new StringBuilder("计算机").append("软件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); } }
jdk7,方法区还由永久代实现,可以比较容易的出发方法区溢出,jdk8之后方法区由元空间实现,理论上仅受限于系统内存,同时可以实现垃圾回收,很难出现方法区溢出。但是HotSpot提供了若干防御性参数避免元空间的任意使用。例如:
-XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。
-XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。
直接内存(DirectMemory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致。由直接内存导致的内存溢出,一个明显的特征是在HeapDump文件中不会看见有什么明显的异常情况,如果读者发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。