第2章 Java内存区域与内存溢出异常

学习目标:明白虚拟机里面的内存是如何划分的,哪部分区域,什么样的代码和操作可能会导致内存溢出异常。

 

2.2 运行时数据区域

 

2.2.1 程序计数器

是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。
 

2.2.2 java 虚拟机栈

    每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表,操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配
    其中的两种异常情况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出stackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
 

2.2.4 Java堆

    他是Java虚拟机所管理的内容中最大的一块。被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例与数组都在这里分配内存。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将会抛出OutOfMemoryError异常。
 

2.2.5 方法区

    与Java堆一样是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。但是这部分的回收确实是有必要的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
 
    使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
    使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。
 

2.4.1 Java堆溢出

    Java堆用于存储对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。
    如果不存在泄露,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长,持有状态时间过长到的情况,尝试减少程序运行期的内存消耗。
 
Java堆内存溢出异常测试
/*
 * vm Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * 
 */
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<OOMobject>();
 
            while(true){
                list.add(new OOMobject());
            }
        }
    }
}
 
运行结果:
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 HeapOOM$OOMobject.main(HeapOOM.java:11)
 
虚拟机栈和本地方法栈OOM测试
/*
 * VM Args: -Xss128k
 */
public class JavaVMStackSOF {
 
    private int stackLength = 1;
 
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }
 
    public static void main(String[] args) {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try{
            oom.stackLeak();
        }catch(Throwable e){
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
 
}
 
运行结果:
stack length:1000
Exception in thread "main" java.lang.StackOverflowError
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
-Xoss参数(设置本地方法栈大小)        -Xss参数(设置栈容量)
 
运行时常量池导致的内存溢出异常
/*VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
import java.util.ArrayList;
import java.util.List;
 
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());
        }
    }
}
 
运行结果:
程序运行了20分钟,都没有结果
 
 
 
 
原文地址:https://www.cnblogs.com/zhoujunok/p/11652969.html