JVM内存溢出是JVM调优里的经常会遇见的场景,下面我就来详解4种常见的JVM内存溢出及解决方案@mikechen
1.堆内存溢出
JVM堆是用于存储对象实例的内存区域,当应用程序创建了太多的对象并且堆空间不足时,就会出现堆内存溢出错误。
比如:
List<Integer> list = new ArrayList<>(); while (true) { list.add(new Integer(1)); }
这段代码会不断地向List中添加Integer对象,导致堆空间不足,最终导致堆JVM内存溢出。
堆内存溢出解决方案:
- 增加堆空间大小,可以通过JVM参数-Xmx和-Xms来设置初始堆大小和最大堆大小;
- 优化代码,减少对象的创建和存储;
- 对于一些大对象,可以考虑使用分段加载或分页加载的方式。
2.栈内存溢出
Java虚拟机中的每个线程都有一个私有的栈,用于存储方法调用和本地变量。
如果递归调用层数过多或者栈空间不足时,就会出现栈内存溢出错误,示例:
public void recursiveMethod(int i) { recursiveMethod(i + 1); }
这段代码中的递归调用会不断地创建新的栈帧,导致栈空间不足,最终导致栈内存溢出。
栈内存溢出解决方案:
- 增加栈空间大小,可以通过JVM参数-Xss来设置;
- 优化代码,减少递归调用;
- 对于需要进行大量递归计算的场景,可以使用尾递归或迭代的方式。
3.永久代内存溢出
JVM的永久代用于存储类信息、方法信息和静态变量等数据,当应用程序创建太多的类或者字符串并且永久代空间不足时,就会出现永久代内存溢出错误。
比如:
public class Test { public static void main(String[] args) { String str = "Test"; while (true) { str += str + new Random().nextInt(99999999); } } }
这段代码中的字符串不断地进行拼接并创建新的字符串对象,导致永久代空间不足,最终导致永久代内存溢出。
永久代内存溢出解决方案:
- 增加永久代空间大小(可以通过JVM参数-XX:MaxPermSize来设置);
- 优化代码,减少字符串拼接操作;
- 对于需要进行大量字符串拼接的场景,可以使用StringBuilder或StringBuffer。
4.方法区内存溢出
Java方法区用于存储类信息、方法信息和静态变量等数据,当应用程序创建太多的类或者字符串并且方法区空间不足时,就会出现方法区内存溢出错误。
比如:
public class Test { public static void main(String[] args) { for (int i = 0; i < 1000000; i++) { String className = "TestClass" + i; byte[] byteCode = generateByteCode(className); Class clazz = defineClass(className, byteCode, 0, byteCode.length); clazz.newInstance(); } } public static byte[] generateByteCode(String className) { String classDef = "public class " + className + " { public void test() {} }"; return classDef.getBytes(); } }
这段代码会不断地创建新的类,并加载到方法区中,导致方法区空间不足,最终导致方法区内存溢出。
方法区内存溢出解决方案:
- 增加方法区空间大小(可以通过JVM参数-XX:MaxMetaspaceSize来设置);
- 优化代码,减少动态生成类的数量;
- 对于需要动态生成类的场景,可以使用CGLIB或Javassist等工具,避免大量类的动态生成。
以上就是JVM内存溢出详解,更多JVM内容请查看:JVM(Java虚拟机)从0到1全部合集
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》