在 java 开发中,内存溢出异常是影响程序稳定性的关键问题。了解其原理和应对方法,对开发者至关重要。
一、java 堆溢出
原理
java 堆用于存储对象实例。不断创建对象,且阻止垃圾回收器回收,对象数量超出堆容量时,就会引发堆溢出。
示例代码
// vm args: -xmx20m -xms20m -xx:+heapdumponoutofmemoryerror
public class heapoom {
static class oomobject {}
public static void main(string[] args) {
list<oomobject> list = new arraylist<>();
while (true) {
list.add(new oomobject());
}
}
}解决思路
- 利用内存映像分析工具(如 eclipse memory analyzer )分析堆转储快照。
- 区分内存泄漏和内存溢出:若存在无用对象长期占用内存,是内存泄漏;若对象都有用但堆空间不足,可调整堆参数(-xmx 与 - xms ),并优化代码减少内存占用。
二、虚拟机栈和本地方法栈溢出
原理
- 线程请求栈深度超虚拟机允许值,抛出
stackoverflowerror。 - 虚拟机栈若支持动态扩展,扩展时内存申请失败,抛出
outofmemoryerror(hotspot 不支持栈动态扩展 )。
示例代码
测试stackoverflowerror
// vm args: -xss128k
public class javavmstacksof {
private int stacklength = 1;
public void stackleak() {
stacklength++;
stackleak();
}
public static void main(string[] args) throws throwable {
javavmstacksof oom = new javavmstacksof();
try {
oom.stackleak();
} catch (exception e) {
system.out.println("stack length:" + oom.stacklength);
throw e;
}
}
}测试大量线程导致内存溢出
// vm args: -xss2m
public class javavmstackoom {
private void dontstop() {
while (true) {}
}
public void stackleakbythread() {
while (true) {
thread thread = new thread(() -> dontstop());
thread.start();
}
}
public static void main(string[] args) throws throwable {
javavmstackoom oom = new javavmstackoom();
oom.stackleakbythread();
}
}解决思路
- 出现
stackoverflowerror时,可根据错误堆栈分析递归调用等问题代码。 - 对于大量线程导致的内存溢出,可减少线程数量、调整栈内存大小(-xss ),或升级 64 位虚拟机以获取更多内存。
三、方法区和运行时常量池溢出
原理
- 方法区存储类信息、常量池等。运行时动态生成大量类(如使用 cglib ),会耗尽方法区空间。
- 运行时常量池是方法区一部分,字符串操作(如
string.intern())不当,可能导致常量池溢出。
示例代码
方法区溢出测试(借助 cglib )
// vm args: -xx:permsize=10m -xx:maxpermsize=10m
import net.sf.cglib.proxy.enhancer;
import net.sf.cglib.proxy.methodinterceptor;
import net.sf.cglib.proxy.methodproxy;
public class javamethodareaoom {
static class oomobject {}
public static void main(string[] args) {
while (true) {
enhancer enhancer = new enhancer();
enhancer.setsuperclass(oomobject.class);
enhancer.setusecache(false);
enhancer.setcallback(new methodinterceptor() {
@override
public object intercept(object obj, java.lang.reflect.method method, object[] args, methodproxy proxy) throws throwable {
return proxy.invokesuper(obj, args);
}
});
enhancer.create();
}
}
}运行时常量池溢出测试(string.intern() )
// jdk 6 运行:-xx:permsize=6m -xx:maxpermsize=6m
// jdk 7及以上运行:-xmx6m
public class runtimeconstantpooloom {
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);
}
}解决思路
- 方法区溢出时,调整方法区相关参数(如 jdk 8 前的 - xx:permsize 和 - xx:maxpermsize ,jdk 8 及以后的 - xx:metaspacesize 等 ),优化代码减少动态类生成。
- 针对常量池溢出,合理使用
string.intern()方法,避免无意义的字符串入池操作。
四、直接内存溢出
原理
直接内存容量由-xx:maxdirectmemorysize 参数控制,默认与 java 堆最大值相同。直接或间接使用directbytebuffer 、unsafe 等分配内存超出限制,会引发溢出。
示例代码
// vm args: -xmx20m -xx:maxdirectmemorysize=10m
import sun.misc.unsafe;
import java.lang.reflect.field;
public class directmemoryoom {
private static final int _1mb = 1024 * 1024;
public static void main(string[] args) throws exception {
field unsafefield = unsafe.class.getdeclaredfields()[0];
unsafefield.setaccessible(true);
unsafe unsafe = (unsafe) unsafefield.get(null);
while (true) {
unsafe.allocatememory(_1mb);
}
}
}解决思路
- 合理设置
-xx:maxdirectmemorysize参数。 - 排查代码中直接内存分配操作,如 nio 相关代码,确保内存分配合理。
ld.get(null);
while (true) {
unsafe.allocatememory(_1mb);
}
}
}解决思路
- 合理设置`-xx:maxdirectmemorysize` 参数。
- 排查代码中直接内存分配操作,如 nio 相关代码,确保内存分配合理。
通过深入理解 java 内存溢出异常原理,结合具体代码示例和解决思路,开发者能更好地定位和解决内存问题,保障 java 程序稳定运行。
到此这篇关于java 聚焦 outofmemoryerror 异常问题记录的文章就介绍到这了,更多相关java outofmemoryerror 异常内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论