JVM关于OOM异常的调优

一、java堆溢出

import java.util.ArrayList;
import java.util.List;

public class HeapOOM {
		static class OOMobject{}
			public static void main(String[] args) {
				List<OOMobject> list = new ArrayList<HeapOOM.OOMobject>();
				while(true){
					list.add(new OOMobject()); 
				}
			}
}

上述代码将会产生下面的异常现象:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:2760)
	at java.util.Arrays.copyOf(Arrays.java:2734)
	at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
	at java.util.ArrayList.add(ArrayList.java:351)
	at cn.xugui.HeapOOM.main(HeapOOM.java:11)

上面就是堆内存溢出异常。解决办法:

1.-XX:HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常的时候Dump内存堆转存快照;

2.通过工具分析上述中的堆转存快照

3.主要分辨出堆内存中的对象是否是需要的,也就是分辨出上述异常到底是内存泄漏还是内存溢出,前者中的对象是不需要的,而后者中的对象是需要的;

   如果是内存泄漏,需要进一步查看泄漏对象到GC Roots的引用链,了解GC Roots的对象是通过怎么样的路径和泄漏对象相互关联而导致GC 无法对其进行回收;如果是内存溢出,则纯粹是对内存不够,需要检查参数-xms和-xmx(设置的堆内存的最小值和最大值),需要和实际的物理内存比较之后,决定是否可以调大,再者,从代码上检测是否存在某些对象的生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗(尝试回收这些对象);

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

该区域会产生两种类型的异常,描述如下:

第一种,如果需要申请栈的深度大于栈的深度,statckOverFlow异常;

第二种,如果栈扩展时无法申请到足够的内存空间,则会报出OutOfMemory异常。

第一种异常的代码:

public class StackOF {
		private static int stackLength = 1;
		public void stackLeak(){
			stackLength++;
			stackLeak();
		}
		public static void main(String[] args) {
			StackOF stackOF = new StackOF();
			try{
				stackOF.stackLeak();
			}catch (Exception e) {
				System.out.println("stackLength:"+stackLength);
			}
			
		}
}

结果:

Exception in thread "main" java.lang.StackOverflowError
	at cn.xugui.StackOF.stackLeak(StackOF.java:6)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	at cn.xugui.StackOF.stackLeak(StackOF.java:7)
	......

在多线程工作下会发生OOM异常(但是不是上述第二种锁表达的含义)

-xss(栈内存的大小,在Hotspot中不区分java虚拟机栈和本地方法栈的)越大,反而更加容易发生OOM异常,原因?

首先操作系统分配给每个进程的内存是有限制的,所以我们可以得到用户线程所能得到的内存为:操作系统分配给JVM进程的总内存 - Xmx(最大堆容量) - MaxPermSize(方法区容量) - 程序计数内存(忽略不计) - 虚拟机本身内存;所以当虚拟机栈的内存越大,线程所能拥有的内存越少,建立线程的数量会变少,建立线程也越容易把剩余的内存消耗殆尽,当线程内存不够的时候,将会产生OOM异常。

三、方法区和运行时常量池的溢出

首先需要解释的是:方法区和永久代的关系,参考https://www.jianshu.com/p/66e4e64ff278

总得来说,在Hotspot中永久代就是对方法区的一种实现。但是,在jdk1.8中,取消了永久代的概念,改为元空间,元空间存储在元空间,元空间被存储在与堆内存不连续的本地内存区域。所以,不会出现向永久代那样的内存溢出问题。而且,运行时常量池在jdk1.7之后被从方法区中剥离出来,放入堆中,因为方法区会因为大量字符串的存在而产生OOM异常。

String string = "12"; //常量,位于常量池中
System.out.println(string.intern() == string);
//结果:true
			
String str = new String("java"); //对象,是在java堆中
System.out.println(str.intern() == str);
//结果:false

我们需要理解常量池的本质,其实常量池就是java提供的一种缓存机制,而字符串常量池是一类的特殊的常量池。它主要提供的方法有两种:

其一是:通过双引号生成的字符串对象,直接被放入常量池中。

其二是:通过String提供的intern方法,如果该对象在常量池中已经存在则直接返回常量池中字符串的引用,反之不存在,则需要将字符串复制到常量池中,返回该常量池中字符串的引用。

注意:上述代码String str = new String("java");如果在字符串常量池中还没有“java”字符串变量时,该代码将会创建两个对象,一个对象是字符串常量池中的“java”对象,一个则是堆中的对象。

查看String的构造方法:

 public String(String original) {
	int size = original.count;
	char[] originalValue = original.value; //这个字符数组用于存储的
	char[] v;
  	if (originalValue.length > size) {//如果存储数组长度大于实际存放的数组长度
 	    // The array representing the String is bigger than the new
 	    // String itself.  Perhaps this constructor is being called
 	    // in order to trim the baggage, so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
 	} else {
 	    // The array representing the String is the same
 	    // size as the String, so no point in making a copy.
	    v = originalValue;
 	}
	this.offset = 0;
	this.count = size;
	this.value = v;
    }

其中Original数组就是字符串常量池中的字符串。

参考:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值