java内存区域与内存溢出异常

 运行时数据区域

     

     

程序计数器(Program Counter Register)

  程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。CPU的计算时间是以分片的方式给到每个线程的(换句话说,所谓的并行其实本质上还是串行),比如线程A执行到了一个地方,CPU将控制权给了线程B,那么线程A重新获得CPU的资源时,如何恢复到刚才执行的地方呢?程序计数器能帮助线程A找到刚刚执行的地方,从而继续刚刚的执行。
  为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。所以程序计数器是线程私有的。

        当线程在执行一个Java方法时,该计数器记录的是正在执行的虚拟机字节码指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。
  另外,程序计数器是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

  Java虚拟机栈所占有的内存空间也就是平时所说的“栈内存”,并且也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧用于存储局部变量表(基本数据类型对象的引用returnAddress类型)、操作数栈、动态链接、方法出口等信息
  局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
  每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。对于Java虚拟机栈,有两种异常情况:

      1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常; 
      2)如果虚拟机栈在动态扩展时,无法申请到足够的内存,就会抛出OutOfMemoryError;

       局部变量表

       局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和returnAddress类型指向一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,即在Java程序被编译成Class文件时,就确定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

      操作数栈

      操作数栈又常被称为操作栈,操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1,64为数据类型所占的栈容量为2。当一个方法开始执行时,它的操作栈是空的,在方法的执行过程中,会有各种字节码指令(比如:加操作、赋值元算等)向操作栈中写入和提取内容,也就是入栈和出栈操作

      动态连接

      每个栈帧包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。


      方法返回地址

      当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。

     方法退出的过程实际上等同于把当前栈帧出站,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。

 

本地方法栈

  本地方法栈和虚拟机栈所发挥的作用非常相似,它们之间的区别主要是,虚拟机栈是为虚拟机执行Java方法(也就是字节码)服务的,而本地方法栈则为虚拟机使用到的Native方法服务
  与虚拟机栈类似,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

Java堆

  Java堆是Java虚拟机所管理的内存中最大的一块。Java堆在主内存中,是被所有线程共享的一块内存区域,其随着JVM的创建而创建,堆内存的唯一目的是存放对象实例和数组。同时Java堆也是GC管理的主要区域
  Java堆物理上不需要连续的内存,只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

  方法区是所有线程共享的一个内存区域。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也有一个别名叫做Non-Heap(非堆),用于与Java堆区分。对于HotSpot虚拟机来说,方法区又习惯称为“永久代”(Permancent Generation),但这只是对于HotSpot虚拟机来说的,其他虚拟机的实现上并没有这个概念。相对而言,垃圾收集行为在这个区域比较少出现,但也并非不会来收集,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载上
  根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。

运行时常量池

  运行时常量池属于方法区。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量表,用于存放编译期生成的各种字面常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放(JDK1.7开始,常量池已经被移到了堆内存中了)。也就是说,这部分内容,在编译时只是放入到了常量池信息中,到了加载时,才会放到运行时常量池中去。运行时常量池是归于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。
  当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常,常量池属于方法区,同样可能抛出OutOfMemoryError异常。

       

       在多线程情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。操作系统为每个进程分配的内存是有限制的,虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,忽略掉程序计数器消耗的内存(很小),以及进程本身消耗的内存,剩下的内存便给了虚拟机栈和本地方法栈,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少。因此,如果是建立过多的线程导致的内存溢出,在不能减少线程数的情况下,就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。
       

      内存泄露是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。Java中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对

      内存溢出是指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。

 对内存分配情况分析最常见的示例便是对象实例化:

                              Object obj = new Object();

       这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中,及时对JVM虚拟机不了解的Java使用这,应该也知道obj会作为引用类型(reference)的数据保存在Java栈的本地变量表中,而会在Java堆中保存该引用的实例化对象,但可能并不知道,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。

       由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄池和直接使用指针。

      通过句柄池访问的方式如下:

      

 

      通过直接指针访问的方式如下:

         

 

    这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存放的是稳定的句柄地址,在对象呗移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处是速度快,它节省了一次指针定位的时间开销。目前Java默认使用的HotSpot虚拟机采用的便是是第二种方式进行对象访问的。

参考

https://blog.csdn.net/eson_15/article/details/51745671

posted on 2018-09-29 22:32  溪水静幽  阅读(118)  评论(0)    收藏  举报