JVM内存区域异常模拟

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二者中的最小值

参考链接:

https://www.cnblogs.com/sxdcgaq8080/p/7156227.html

https://www.cnblogs.com/sxdcgaq8080/p/7196580.html

猜你喜欢

转载自blog.csdn.net/u014106644/article/details/88377009