自动内存管理机制之java内存区域与内存溢出异常

一、运行时数据区域

  java虚拟机所管理的内存会包括下面的几个部分:

1.程序计数器:是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。一般情况下,字节码解释器工作的时候就是通过改变计数器的之来选取需要执行的字节码指令。

  (1)每条线程都有一个独立的程序计数器,每个线程都有一个独立的程序计数器,各个线程之间的计数器互不影响独立存储,这类内存区是线程私有的。

  (2)此内存区域没有任何OutOfMemoryError的情况。

2.java虚拟机栈

  (1)线程私有,生命周期与线程相同

  (2)描述的是java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接、方法出口等信息,每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

  (3)java内存一般分法:堆内存(heap);栈内存。这里的栈就是虚拟机栈或者说是虚拟机中局部变量的部分。局部变量存放了各种基本的数据类型。局部变量所需的内存空间是在编译期完成分配的

  (4)线程请求深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常;

   如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3.本地方法栈(Native Method Stack)

  (1)本地方法栈与虚拟机栈所发挥的作用是非常相似的,区别是:虚拟机栈为虚拟机执行java方法服务,本地方法栈则为虚拟机使用到的Native方法服务

4.java堆(java Heap)

  (1)java虚拟机中管理内存中最大的一块。被所有线程贡献的内存区域,在虚拟机启动的时候创建。java堆得唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。

  (2)java堆是垃圾收集器管理的主要区域,很多时候被称为GC堆。分代收集算法划分java堆:新生代和老年代,Eden空间、From Survivor空间、To Survivor空间。在java堆中无论怎么划分空间,存放的都是对象实例。对java堆进一步划分的目的是为了更好的回收内存。

  (3)java堆可以处于物理上不连续的内存空间,只要逻辑上连续就可以,当前主流的虚拟机都是可以扩展的。

5.方法区

  (1)各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、编辑器编译后的代码等。

  (2)方法区有个别名:Non-Heap(非堆)。不连续内存和可以选择固定大小或者可扩展,还可以不识闲垃圾收集

6.运行时常量池(Runtime COnstant Pool):方法区的一部分

7.直接内存

三、HotSpot虚拟机对象探秘

  1.对象的创建。

    (1)对象的创建过程:首先检查指令的参数是否能在常量池中定位到一个类的符号的引用,并且检查这个符号的引用代表的类是否已被加载、解析和初始化过。如果没有那就先要执行相应的类加载的过程。

    (2)为新生对象分配内存:在类加载完成后,对象所需的内存大小就可以完全确定。内存分配方式是指针碰撞。

  2.对象的内存布局

    (1)对象在内存中的布局分为3块区域:对象头(Header)、实例数据(Instance Data)和对其填充(Padding)

    (2)对象头包括两部分:第一部分是存储对象自身的运行时数据;第二部分时类型指针,虚拟机通过这个指针确定对象是哪个类的实例。

    (3)实例数据是对象真正存储有效信息,即在程序代码中定义的各种类型的字段内容。

  3.对象的访问定位

    (1)目前主流的访问方式是有使用句柄和直接指针两种。

    (2)句柄访问。java堆中会划出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。这种方式的好处是reference中存储的是稳定的句柄地址,在对象被移动的时候只会改变句柄中实例数据指针,而reference本身不会修改

四、OutOfMemoryError异常

  1.java堆溢出

    (1)只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆得容量限制之后就会产生内存溢出异常,如下所示的代码中

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<>();
        while(true){
            list.add(new OOMObject());
        }
    }
}
//java堆内存溢出的时候,异常信息是

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


    对于上面的问题,要解决堆内存溢出的问题,一般手段是通过内存分析工具堆溢出的堆转储快照进行分析。如果时内存泄漏就找出相应的位置;如果不存在内存的泄漏,就应该检查虚拟机的堆参数,与机器的物理内存看看是否还可以继续调大。

  2.虚拟机栈与本地栈溢出

    (1)栈容量的设置:由-Xss参数设定。

    (2)关于虚拟机栈和本地方法栈有两种异常:

      线程请求的栈深度大于虚拟机所允许的最大深度,就会抛出StackOverflowError异常(栈空间太小还是已经使用的栈空间太大?)

      如果虚拟机在扩展栈的时候无法申请到足够的内存空间,那么会抛出OutOfMemoryErroer异常()

public class JavaVMStackSOF {
    private int stackLength=1;
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }
    public static  void main(String[] args) throws Throwable{
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        }catch (Throwable e){
            System.out.println("stack length"+oom.stackLength);
        }

    }
}
//在单个线程的情况下,无论是栈帧太大还是虚拟机容量太小,当内存无法分配的时候,虚拟机跑出的都是StackOverflowError

  3.方法区和运行时常量池溢出

  4.本机直接内存溢出

    

    

 

 

  

posted @ 2019-02-28 17:02  stone1234567890  阅读(191)  评论(0编辑  收藏  举报