JVM初探-Java运行时数据区划分

原创
2018/09/02 23:33
阅读数 178

Java虚拟机在运行程序时会把它所管理的内存分为若干个不同的数据区域,每个区域都有其用途。有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。Java虚拟机运行时数据区划分如图:

程序计数器(Program Counter Register)

程序计数器(以下简称PCR)并不是在所有的Java虚拟机中都存在,不同虚拟机可以通过其他的方式去实现程序计数器的功能。PCR可以看成当前线程所执行的字节码的行号指示器,记录Java程序已经执行到了哪里,主要服务于字节码解释器。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖PCR来完成。PCR所占的内存空间较小。

因为Java多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。所在在确定时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的PCR,线程之间计数器互不影响,这属于"线程私有"内存。

如果线程正在执行Java方法,这个计数器记录的正在执行的虚拟机字节码指令地址,如果执行的是Native方法,这个计数器值则为空。注意:此内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM情况的区域。

再次说明:PCR属于线程私有。

Java虚拟机栈(VM Stack)

和PCR一样,Java虚拟机栈(以下简称VM Stack)也是线程私有,生命周期和线程相同。VM Stack描述Java方法执行的内存模型:每个方法在执行时都会创建栈帧(Stack Frame)用于存储局部变量表,操作数帧,动态链接,方法出口等信息。每一个方法从调用到执行完成的过程,就是一个栈帧在VM Stack中入栈出栈的过程。

很多程序员将JVM内存分为堆内存和栈内存。栈内存就是VM Stack中的局部变量表。其存放了编译器可预知的各种基本数据类型:boolean, byte, char, short, int, float. long, double(long, double会占用两个局部变量空间Slot,其他类型只占用一个),对象引用(reference类型,它不等于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向一条字节码指令的地址)。局部变量表所需要的内存空间会在编译期间完成分配,当进入一个方法,这个方法需要的分配多大的局部变量空间是完全确定的,运行期间不会改变局部变量空间。

在JVM规范中,对这个VM Stack规定了两种异常:如果线程请求的栈深大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果VMstack在动态扩展时无法申请到足够的内存,就会抛出OOM异常。

本地方法栈(Native Method Stack)

本地方法栈(以下简称NMS)和VM Stack类似,只不过不为Java方法服务,而是JVM所使用到的Native方法。和VMStack一样,NMS也会抛出StackOverflowError异常和OOM异常。

Java堆(Java Heap)

Java堆(以下简称Heap)是JVM管理的内存中最大的一块。Heap是被所有线程共享的内存区域,JVM启动自动创建,此内存区域唯一目的就是存储Java对象实例。几乎所有的Java对象都会在这个区域上分配内存,只是绝大多数,随着JIT编译器的发展和逃逸分析技术的成熟,栈上分配,标量替换等优化技术将会改变对象实例分配内存的方式。在Heap上分配内存则变得不是那么绝对了。

Heap是GC管理的主要区域。从GC角度看,现在很多GC都采用分代收集算法,所以Heap可分为:新生代,老年代;还可以分为 Eden空间,From Survivor空间,To Survivor空间等。后续慢慢探讨。

Heap在物理上可以存在于不连续的内存空间,只要逻辑连续即可,也可以是扩展的。如果对象实例分配没有完成,或者堆无法继续扩展,将会抛出OOM异常。

方法区(Method Area)

方法区(以下简称MA)与Java 堆一样,为线程共享区域。它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是他却有一个别名 Non-Heap(非堆),目的是将Java堆区分开。很多虚拟机例如HotSpot用GC分代回收机制中的永久代去实现方法区,所以很多人管方法区叫永久代,但两者并不等价。用永久代去实现方法区,更容易出现内存溢出问题。

GC对方法区的回收是很少出现,但是并不是完全没有,并非完全的“永久”,对方法区的回收主要是针对常量池的回收和对类型的卸载。

当方法区无法满足内存分配需求时,会抛出OOM异常。

运行时常量池(Runtime Contant Pool)

运行时常量池(Runtime Contant Pool)是方法区的一部分,class 文件除了有类的版本,字段,方法,接口等描述信息,还有一项重要信息是常量池(Contant Pool Table), 用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。

展开阅读全文
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部