演示环境如下:
- JDK1.8
前文我们针对JVM运行时区域划分做了介绍,明确了各区域的作用,以及可能出现的异常,烦请思考以下问题:
什么样的代码会让指定区域反生OutOfMemoryError(OOME)或者StackOverflowError(SOE)呢?
这个问题你也许会感到困惑,为什么要去了解会出现OOME或者SOE的代码?实际上我们只有对OOME或者SOE有了清楚的认识,才能合理的避免问题的产生,以及发生问题时能够可以有效的解决。
Java堆:OutOfMemoryError
首先在“构建”Java堆的OutOfMemoryError时,我们应对Java堆有充足的认识,只有知道堆的作用,才能知道为何Java堆会发生OutOfMemoryError;Java堆中存储的是对象的实例,这也是我们的模拟此问题的出发点,“构建”Java堆的OutOfMemoryError参考以下代码示例:
代码调试
调整虚拟机启动参数:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
package sunce.xin.education.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* Java堆OutOfMemoryError
* @author lowrie
* @date 2019-05-05
*/
public class HeapErrorTest {
public static void main(String[] args) {
List<HeapErrorTest> list = new ArrayList<>();
while (true) {
list.add(new HeapErrorTest());
}
}
}
Error输出
Java堆dump文件分析
在发生OutOfMemoryError时我们可以导出dump文件,利用内存分析工具来定位问题产生的原因,笔者常用的内存分析工具是Jprofiler,如下是通过Jprofiler分析以上代码dump的示例:
dumpJava虚拟机栈:StackOverflowError
Java虚拟机栈是由线程创建用于描述Java方法的执行,栈的大小越大可执行的方法深度越深,为了方便“构建”StackOverflowError,我们修改启动参数如下:
代码调试
调整虚拟机启动参数:-Xss256k
package sunce.xin.education.jvm;
/**
* 栈StackOverflowError
* @author lowrie
* @date 2019-05-05
*/
public class StackErrorTest {
private int length = 0;
public static void main(String[] args) {
StackErrorTest object = new StackErrorTest();
try {
object.callMethod();
} catch (Throwable e) {
System.out.println("stack deep length:" + object.length);
e.printStackTrace();
}
}
public void callMethod() {
while (true) {
length += 1;
callMethod();
}
}
}
Error输出
stack实验结果表明,无论是栈帧太大,或者栈内存空间过小,当无法分配内存时,虚拟机都会抛出StackOverflowError.
Java虚拟机栈:OutOfMemoryError
我们知道当虚拟机扩展栈时,如果无法申请到足够的内存空间,则会抛出OutOfMemoryError;操作系统分配给每个进程的内存空间是有限的,假如你的操作系统内存只有2g,减去分配给堆的Xmx(最大堆大小),再减去分配给元数据的MaxMetaspaceSize/MaxPermSize,程序计数器占用的空间很小可以忽略,剩余的就被Java虚拟机栈和本地方法栈瓜分,所以每个栈空间分配的越大,所能创建的线程数越少。
代码调试
调整虚拟机启动参数:-Xss2m
package sunce.xin.education.jvm;
/**
* 栈空间无法分配导致的内存溢出
* 虚拟机参数 -Xss2m
*
* @author lowrie
* @date 2019-05-06
*/
public class StackCauseOutOfMemoryErrorTest {
private void stackLeak() {
while (true) {
}
}
private void currentTest() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
stackLeak();
}
});
thread.start();
}
}
public static void main(String[] args) {
StackCauseOutOfMemoryErrorTest test = new StackCauseOutOfMemoryErrorTest();
test.currentTest();
}
}
Error输出
stack方法区:OutOfMemoryError
我们知道方法区(元数据区)是用来保存类的描述信息,以及常量池等信息;所以“构建”此区域的OutOfMemoryError,出发点也是针对其保存的信息;我们通过以下实例来构建:
代码调试
调整虚拟机启动参数:
JDK1.8 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
JDK1.7 -XX:PermSize=10m -XX:MaxPermSize=10m
package sunce.xin.education.jvm;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Meta/Method Area OutOfMemoryError
* JDK1.8 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*
* @author lowrie
* @date 2019-05-05
*/
public class MetaErrorTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MetaErrorTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
}
Error输出
Metaspace发现过程
添加-verbose:class参数,观察class的加载过程
-verbose:class -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
异常输出如下:
类加载过程
附录 JVM常用参数
参数 | 示例 | 说明 |
---|---|---|
-Xsssize | -Xss1m | 设置线程栈大小 |
-Xmssize | -Xms1m | 设置堆初始大小 |
-Xmxsize | -Xmx1m | 设置堆最大大小 |
-Xmnsize | -Xmn1m | 设置年轻代最大大小 |
-XX:NewSize=size | -XX:NewSize=10m | 设置年轻代初始大小 |
-XX:MetaspaceSize=size | -XX:MetaspaceSize=10m | 设置元数据区大小 |
-XX:MaxMetaspaceSize=size | -XX:MaxMetaspaceSize=10m | 设置最大元数据区大小 |
-verbose:class | -verbose:class | 显示类加载信息 |
-verbose:gc | -verbose:gc | 显示GC信息 |
网友评论