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

来源:互联网 发布:于正 笑傲江湖 知乎 编辑:程序博客网 时间:2024/05/01 03:39
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() {@Overridepublic 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
原创粉丝点击