前言
本篇博文主要介绍使用程序触发对应的内存溢出,并附带上JVM常用的命令,供以后查看使用。
堆溢出
堆主要是用来存储对象,我们只要不断的创建对象,并防止虚拟机对对象进行回收则可以触发堆溢出。
-
-Xms
设置堆最小值、-Xmx
设置堆最大值。如果两者相同,则可以避免堆自动扩展; -
-XX:+HeapDumpOnOutOfMemoryError
可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析
/**
* Title:HeapOom 堆溢出
* Description: VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
* @author lin.xu
* @date 2017/12/4.
*/
public class HeapOom {
static class OomObject {
}
public static void main(String[] args) {
List<OomObject> list = new ArrayList<OomObject>();
while (true) {
list.add(new OomObject());
}
}
}
执行过后,会提示java.lang.OutOfMemoryError: Java heap space
。
解决方法:
通过内存映像分析工具(如Eclipse Memory Analyzer、JProfiler)对Dump出来的堆转储快照进行分析,重点先确定是内存泄漏还是内存溢出。如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,掌握泄漏对象的类型信息及GC Roots引用链信息就可以定位泄漏代码位置;如果不是内存泄漏,则可以检查
-Xms
和-Xmx
参数设置,另外查看下代码判断对象生命周期是否过长。
虚拟机栈和本地方法栈溢出
栈内存容量的计算由操作系统为虚拟机分配的内存,扣除最大堆内存和方法区占用的最大内存之后所剩余的,由于程序计数器占用内存小可以忽略不计。
-
-Xss
设置虚拟机栈大小,-Xoss
设置本地方法栈大小。但是对于HotSpot来说,-Xoss
是无效的,因为HotSpot中根本不区分本地方法栈;
/**
* Title:JavaVmStackSof 栈溢出
* Description: -Xss180k
*
* @author lin.xu
* @date 2017/12/4.
*/
public class JavaVmStackSof {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVmStackSof jvss = new JavaVmStackSof();
try {
jvss.stackLeak();
} catch (Throwable e) {
System.out.println("Stack size: " + jvss.stackLength);
throw e;
}
}
}
执行过后,会提示java.lang.StackOverflowError
。
解决方法:
一般可以依据抛出的异常直接定位到代码的位置。如果是由于过多线程导致的内存溢出,在不能减少线程数的情况下,只能减少最大堆和减少栈容量来换取更多的线程。
方法区和运行时常量池溢出
JDK1.7及之前版本,采用PermSize
及MaxPermSize
来设置方法区大小,由于JDK1.7开始已经在“去永久代”的实现,在JDK9中如果采用上述两个配置运行本节的示例则直接提示
Ignoring option PermSize; support was removed in 8.0。
Ignoring option MaxPermSize; support was removed in 8.0
JDK9中需要将PermSize
替换为MetaspaceSize
,MaxPermSize
替换为MaxMetaspaceSize
。
方法区主要用于存放Class相关的信息,如类名、访问修饰符、常量池、字段描述及方法描述等。测试时主要就是加载大量的类来使方法区溢出。
/**
* Title:JavaMethodAreaOom
* Description:vm args: -XX:PermSize=10M -XX:MaxPermSize=10M
*
* @author lin.xu
* @date 2017/12/5.
*/
public class JavaMethodAreaOom {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OomObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
}
执行过后,会提示java.lang.OutOfMemoryError: PermGen space
。
本机直接内存溢出
DirectMemory容量可以通过-XX: MaxDirectMemorySize
指定,如果不指定,则默认与Java堆最大值相同。
/**
* Title:DirectMemoryOom
* Description:vm args: -Xmx10M/-XX:MaxDirectMemorySize=10M
*
* @author lin.xu
* @date 2017/12/5.
*/
public class DirectMemoryOom {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws IllegalAccessException {
Field field = Unsafe.class.getDeclaredFields()[0];
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
执行过后,会提示java.lang.OutOfMemoryError
。
由DirectMemory导致的内存溢出,明显的特征就是HeapDump文件中不会看见明显的异常。如果发现OOM之后Dump文件很小,而程序中又间接或直接使用了NIO,则可以考虑检查下是不是本机直接内存溢出问题。
JVM Args常用命令列表
命令 | 说明 |
| 设置Java堆最小内存为20M,必须以M为单位 |
| 设置Java堆最大内存为20M,必须以M为单位。如果值与 |
| 设置JVM栈内存大小为128K。在JDK9中,最小必须设置为180k |
| 设置本地方法栈大小为128k。不过在HotSpot虚拟机中,本参数无效。因为HotSpot中不区分JVM栈和本地方法栈 |
| JVM初始分配的永久代容量,必须以M为单位。JDK8过后已无效。 |
| JVM允许分配的永久代最大容量,必须以M为单位。JDK8过后已无效 |
| 关闭JVM对类的垃圾回收 |
| 查看类的加载信息 |
| 查看类的卸载信息 |
| 年轻代:老年代=1:4 |
| 2个Survivor:1个Eden=2:8 |
| 年轻代大小为20M |
| 程序抛出异常时,存储堆内存转储快照 |
| JVM使用G1垃圾收集器 |
| 在控制台上打印出GC具体细节 |
| 在控制台上打印出GC信息 |
| 对象大于3145728(3M)时直接进入老年代分配,这里只能以字节作为单位 |
| 对象年龄大于1,则自动进入老年代 |
| 当一个方法被调用1000次之后,被认为是热点代码,触发即时编译 |
| 查看每次GC前后堆内存布局 |
| 虚拟机是否启动TLAB |
| 查看TLAB的使用情况 |
| 开启自旋锁 |
| 更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁 |