JVM内存区域分为程序计数器,虚拟机栈,本地方法栈,堆区,方法区,运行时常量池以及本地直接内存。
程序计数器,每个线程都有一个独立的程序计数器,作为当前线程执行字节码的行号指示器。如果执行java方法则计数器记录正在执行的虚拟机字节码指令地址,如果执行native方法则该值为空。该区域不会发生OOM异常。
虚拟机栈,线程私有,java方法执行的内存模型。每个方法会创建一个栈帧,每执行一个方法对应一个栈帧的入栈与出栈。如果栈请求深度大于虚拟机允许的深度,会发生StackOverflowError;如果栈区进行扩展时无法伸缩更多的内存,会发生OutOfMemoryError异常。
堆区,线程共享区域,用来存放对象实例。当堆无法扩展时会发生OOM。
方法区,线程共享,存储已被虚拟机加载的类信息,常量,静态变量以及即时编译器编译后的代码等数据。当方法区无法扩展时会发生OOM
运行时常量池,方法区的一部分,存放编译期生成的各种字面量和符号应用。OOM
直接内存,NIO中分配堆外内存,通过堆中引用来使用该内存,避免java堆和native堆来回复制数据。OOM
1.堆区OOM异常
设置堆区最小最大等于20M,程序中不断创建对象最终OOM
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args) {
List<OOMObject> l = new ArrayList<>();
while(true){
l.add(new OOMObject());
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid11188.hprof ...
Heap dump file created [28054114 bytes in 0.198 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.grow(Unknown Source)
at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at error.HeapOOM.main(HeapOOM.java:13)
使用内存映像分析工具EMA分析: 首先判断堆区OOM是由内存泄漏还是内存溢出。
内存泄漏说明有大量对象与GC ROOT缺失关联导致无法回收,内存溢出可能是由于堆区太小对象实例太多。
2.栈区StackOverFlowError异常
设置每个线程栈大小128k,如果线程执行方法深度太大会造成线程栈溢出异常
执行无限递归程序,直到栈溢出
public class StackOverFlowTest {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
System.out.println("------------" + stackLength);
stackLeak();
}
public static void main(String[] args) {
StackOverFlowTest s = new StackOverFlowTest();
try {
s.stackLeak();
} catch (Exception e) {
System.out.println("------------" + s.stackLength);
e.printStackTrace();
}
}
}
------------971
------------972
------------973
------------974
------------975
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.ext.DoubleByte$Encoder.encodeArrayLoop(Unknown Source)
at sun.nio.cs.ext.DoubleByte$Encoder.encodeLoop(Unknown Source)
at java.nio.charset.CharsetEncoder.encode(Unknown Source)
at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
at sun.nio.cs.StreamEncoder.write(Unknown Source)
at java.io.OutputStreamWriter.write(Unknown Source)
at java.io.BufferedWriter.flushBuffer(Unknown Source)
at java.io.PrintStream.write(Unknown Source)
at java.io.PrintStream.print(Unknown Source)
at java.io.PrintStream.println(Unknown Source)
at error.StackOverFlowTest.stackLeak(StackOverFlowTest.java:9)
at error.StackOverFlowTest.stackLeak(StackOverFlowTest.java:10)
3.栈区OOM异常
设置每个线程栈大小为2M,不断创建线程,应该会出现栈区OOM
public class StackOOM {
private int num = 0;
private void dontStop(){
while(true){}
}
public void satckLeakByThread(){
while(true){
num++;
System.out.println(num);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
StackOOM s = new StackOOM();
try {
s.satckLeakByThread();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
一直没有出现OOM,这个深入研究一下。
增大堆内存 增加单个线程的大小 -Xmx6g -Xms6g -Xss2M 还是没有OOM好奇怪
4.运行时常量池OOM
-XX:PermSize=1M -XX:MaxPermSize=1M
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
在jdk1.6中会出现运行时常量池OOM,但在jdk1.8中不会出现OOM
改变堆区大小:-Xmx10m -Xms10m -XX:-UseGCOverheadLimit 会出现堆区OOM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.Integer.toString(Unknown Source)
at java.lang.String.valueOf(Unknown Source)
at error.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:12)
jdk1,6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面。JDK 1.6下,会出现“PermGen Space”的内存溢出,而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。
5.方法区OOM
设置JVM参数:-XX:PermSize=10M -XX:MaxPermSize=10M 不会出现OOM
public class MethodAreaOOM {
// -XX:PermSize=10M -XX:MaxPermSize=10M
// -XX:MetaspaceSize=3M -XX:MaxMetaspaceSize=3M
public static void main(String ss[]) {
while (true) {
Enhancer e = new Enhancer();
e.setSuperclass(OOMObject.class);//要生成OOMObject类的子类
e.setUseCache(false);
e.setCallback(new MethodInterceptor() {//设置callback,对原有对象的调用全部转为调用MethodInterceptor的intercept方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before invoke ");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After invoke");
return result;
}
});
OOMObject ee = (OOMObject) e.create();
ee.test();
}
}
static class OOMObject {
public void test() {
System.out.println("invokinginginginging....");
}
}
}
设置JVM参数会出现OOM -XX:MetaspaceSize=3M -XX:MaxMetaspaceSize=3M
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
6.直接内存OOM
public class DirectMemoryOOM {
private static final int _1MB = 1024*1024;
// -Xmx20M -XX:MaxDirectMemorySize=10M
public static void main(String[] args) throws Exception {
Field f = Unsafe.class.getDeclaredFields()[0];
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at error.DirectMemoryOOM.main(DirectMemoryOOM.java:17)
JDK1.8中新特性:
JDK 1.7 及以往的 JDK 版本中,Java 类信息、常量池、静态变量都存储在 Perm(永久代)里。类的元数据和静态变量在类加载的时候分配到 Perm,当类被卸载的时候垃圾收集器从 Perm 处理掉类的元数据和静态变量。当然常量池的东西也会在 Perm 垃圾收集的时候进行处理。
JDK 1.8 的对 JVM 架构的改造将类元数据放到本地内存中,另外,将常量池和静态变量放到 Java 堆里。HotSopt VM 将会为类的元数据明确分配和释放本地内存。在这种架构下,类元信息就突破了原来 -XX:MaxPermSize 的限制,现在可以使用更多的本地内存。这样就从一定程度上解决了原来在运行时生成大量类的造成经常 Full GC 问题,如运行时使用反射、代理等。
堆区的初始大小:
操作系统及JVM类型 初始堆的大小(Xms) 最大堆的大小(Xmx) Linux/Solaris,32位客户端 16MB 256MB Linux/Solaris,32位服务器 64MB 取1GB和物理内存大小1/4二者中的最小值 Linux/Solaris,64位服务器 取512MB和物理内存大小1/64二者中的最小值 取32GB和物理内存大小1/4二者中的最小值 MacOS,64位服务器型JVM 64MB 取1GB和物理内存大小1/4二者中的最小值 32位Window系统,客户端型JVM 16MB 256MB
64位Window系统,客户端型JVM 64MB 1GB和物理内存大小1/4二者中的最小值
参考链接: