JVM运行时数据区域简介

来源:互联网 发布:java telnet乱码 编辑:程序博客网 时间:2024/06/16 02:26
     Java虚拟机在运行过程中会把所管理的内存划分为若干个不同的区域,有各自的用途,以及创建和销毁的时间。
一、程序计数器
     每条线程都需要有一个独立的程序计数器。
     Program Counter Register,是一块较小的内存空间,是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。当执行Java方法时,计数器记录的是正在执行的虚拟机字节码指令的地址;执行native方法时,计数器值为空。
     没有规定任何OutOfMemoryError异常。

二、虚拟机栈
     线程私有,生命周期与线程相同。
     虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
     局部变量表存放编译期可知的基本数据类型、对象引用类型(对象地址的引用指针)和returnAddress类型,局部变量表的大小在编译期间完成分配,在方法运行过程中不会改变大小。
     如果现场请求的栈深度大于虚拟机栈允许的深度,将抛出StackOverFlowError异常;如果虚拟机栈扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。
  1.测试虚拟机栈和本地方法栈的溢出
     两种异常:当线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverFlowError;当虚拟机在扩展栈时无法申请到足够的内存空间,抛出OOM error。
     测试方式:设置栈的大小,通过无限递归测试达到最大栈深度;通过不断创建线程,来测试栈空间不足。

三、本地方法栈
     作用同虚拟机栈,区别是本地方法栈为虚拟机使用到的Native方法服务。HotSpot虚拟机直接把虚拟机栈和本地方法栈合并;Native方法使用的语言、使用方式与数据结构都没有强制规定。
     异常类型与虚拟机栈相同。

四、Java堆
     所有线程共享,在虚拟机启动时创建。唯一作用就是存放对象实例。
     Java堆可以处于物理上不连续的内存空间,逻辑上连续即可。可维护一个列表来记录哪块内存是可用的。堆大小可扩展(通过-Xmx和-Xms控制)。
     Java堆是垃圾收集器管理的主要区域,Java堆中可细分为新生代和老年代,也可划分出多个线程私有的分配缓冲区,便于内存的回收、分配。
     如果在堆中没有内存完成实例分配,并且堆也无法扩展时,将抛出OutOfMemoryError异常。
  1.测试java堆溢出
     采用不断创建对象,并保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象的方式,设置堆的大小,设置参数让虚拟机dump出内存堆转储快照以便分析。
     运行结果:OOM ERROR:java heap space
     分析步骤:通过内存映像分析工具eclipse memory analyzer分析dump的堆转储快照,分清出现的是内存泄漏还是内存溢出。如果是内存泄漏,可通过工具查看泄漏对象到GC Roots的引用链,就可以准确定位出泄漏代码的位置。
     

五、方法区
     线程共享。
     用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。与堆类似,不需要连续的内存空间,大小可扩展。
     运行时常量池,方法区的一部分,用于存放编译器生成的各种字面量、符号引用和翻译出来的直接引用,在类加载后进入方法去的运行时常量池中存放;同时具有动态性,在运行期间也可将新的常量放入池中。
     方法区的内存回收目标主要是对常量池的回收和对类型的卸载。
     将抛出OutOfMemoryError异常。
  1.测试方法区和常量池溢出
     方法:通过不断创建字符串常量或者不断创建java类
     结果:OOM Error:PermGen space

六、直接内存
     在访问本地内存中的数据时,为了避免数据在Java堆和本地内存中来回复制,NIO类可以使用Native函数库分配堆外内存,通过存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。