探索深入理解java虚拟机之java内存溢出(OOM)解析(3)

java内存溢出(OOM)解析


根据java内存的结构,发生内存溢出的地方在于堆、栈、方法区、直接内存。
1、 Java堆溢出(对象太多)

Java堆用于存储对象实例,只要不断的创建对象,并且保证GCRoots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在数量到达最大堆的容量限制后就会产生内存溢出异常。
通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们的。
掌握了泄漏对象的类型信息及GC Roots引用链的信息,就可以比较准确地定位出泄漏代码的位置。

如果不存在泄露,换句话说,就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

	import java.util.ArrayList;
	import java.util.List;
	/** 
	 * java 堆溢出 
	 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError 
	 * @author  
	 */ 
	public class Stack {
		static class OOMObject {
		}
		public static void main(String[] args) {
			List<OOMObject> list = new ArrayList<Stack.OOMObject>();
			int count = 0;
			while (true) {
				list.add(new OOMObject());
				System.out.println(count++);
			}
		}
	}

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space


2、虚拟机栈和本地方法栈溢出


对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定。
关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:


如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
	public class JVMStackSOF {
		private int staticLength = 1;


		private void stackLeak() {
			staticLength++;
			stackLeak();
		}
		public static void main(String[] args) throws Throwable {
			JVMStackSOF oom = new JVMStackSOF();


			try {
				oom.stackLeak();
			} catch (Exception e) {
				System.out.println("stack length:" + oom.staticLength);
				throw e;
			}
		}
	}

Exception in thread "main" java.lang.StackOverflowError
at demo6.JVMStackSOF.stackLeak(JVMStackSOF.java:8)
在单线程下,无论由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。


如果是多线程导致的内存溢出,与栈空间是否足够大并不存在任何联系,这个时候每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。
解决的时候是在不能减少线程数或更换64为的虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。
	public class javaJVMStackOOM {


		private void dontStop() {
			while (true) {
			}
		}
		public void stacLeakByThread() {
			while (true) {
				Thread thread = new Thread(new Runnable() {


					@Override
					public void run() {
						dontStop();
					}
				});
				thread.start();
			}
		}
		public static void main(String[] args) throws Throwable {
			javaJVMStackOOM oom = new javaJVMStackOOM();
			oom.stacLeakByThread();
		}
	}

//i=391  
//thread num:391  
//Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread  


3、 方法区和运行时常量池溢出(当运行时常量池过大或者类过多时就会导致方法区溢出)


String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
由于常量池分配在永久代中,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量。
	import java.util.ArrayList;  
	import java.util.List;    
	/** 
	 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M 
	 * @author  
	 */  
	public class RuntimeConstantPoolOOM {  
		public static void main(String[] args) {  
			// 使用List保持着常量池引用,避免Full GC回收常量池行为  
			List<String> list = new ArrayList<String>();  
			// 10MB的PermSize在integer范围内足够产生OOM了  
			int i = 0;   
			while (true) {  
				list.add(String.valueOf(i++).intern());  
			}  
		}  
	} 

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 
    at java.lang.String.intern(Native Method) 
    at baby.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18) 

Intern():
JDK1.6 intern方法会把首次遇到的字符串实例复制到永久代,返回的也是永久代中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是一个引用。
JDK1.7 intern()方法的实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。
	public class RunConstantPoolOOM {
		public static void main(String[] args) {
			// 计算机软件字符串常量池在StringBuffer.toString();之前没有引用
			String str1 = new StringBuffer("计算机1").append("软件").toString();
			System.out.println(str1.intern() == str1);


			// java字符串常量池在StringBuffer.toString();之前有引用,关键字
			String str2 = new StringBuffer("dou").append("ble").toString();
			System.out.println(str2.intern()==str2);
		}
	}

1.7 true
false
1.6 false
false

	import java.lang.reflect.Method;   
	import net.sf.cglib.proxy.Enhancer;  
	import net.sf.cglib.proxy.MethodInterceptor;  
	import net.sf.cglib.proxy.MethodProxy;  
	  
	/** 
	 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M 
	 * @author  
	 */  
	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() {  
				  
					@Override  
					public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {  
						// TODO Auto-generated method stub  
						return proxy.invokeSuper(obj, arg);  
					}  
				});  
				enhancer.create();  
			}  
		}  
		static class OOMObject {  
		}  
	} 

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 
at java.lang.String.intern(Native Method) 
at baby.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)


4、 直接内存溢出
虽然使用DerictByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配,而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()。

	import java.lang.reflect.Field;     
	import sun.misc.Unsafe;   
	/** 
	 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M 
	 * @author  
	 * Eclipse 默认把这些受访问限制的API设成了ERROR。 
		解决办法:将Windows->Preferences->Java-Complicer->Errors/Warnings->Deprecated and restricted API,
		中的Forbidden references(access rules)设置为Warning,即可以编译通过。 
	 */  
	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);  
			}  
		}  
	}  

Exception in thread "main" java.lang.OutOfMemoryError 
at sun.misc.Unsafe.allocateMemory(Native Method) 
at baby.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)


每天努力一点,每天都在进步。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

powerfuler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值