JVM的基本结构如下:
主要结构如图: 接下来分别介绍6个数据区的主要用途:
程序计数器占用的大小比较小,每个线程都会创建自己的程序计数器,当然它的生存周期也就跟线程的生存周期保持一直了,它的主要用途是跟踪程序方法执行的路径,它记录了当前方法执行的操作和位置,在进行多线程调度的时候是需要进行CPU分片的,有了程序计数器就可以保护线程的执行情况,当获得CPU使用权的时候能够进行线程恢复.它一般存放的指令为:分支、循环、跳转、异常处理等指令.如果是不是本地方法,这里存放就是字节码指令的地址,如果是本地方法,它的值是undefined.这个区域通常来说比较小,也是JVM唯一没有规定OutOfMemoryError的区域.
这个区域也是线程私有的区域,当每个方法进行执行的时候都会在栈区创建栈帧(stack frame),栈帧一般存放的局部变量表,操作数栈,动态连接与方法出口等信息.局部变量表存放的方法入口还有在编译器就确定的基本数据类型(long与double类型占用两个slot,其余占用一个)和引用类型和returnAddress类型(eturnAddress类型会被Java虚拟机的jsr、ret和jsr_w指令所使用。returnAddress类型的值指向一条虚拟机指令的操作码。与前面介绍的那些数值类的原生类型不同,returnAddress类型在Java语言之中并不存在相应的类型,也无法在程序运行期间更改returnAddress类型的值。)这个区域确定了两种异常情况,当方法调用深度超过JVM的允许值会抛出StackOverflow,如果动态的扩展无法申请到足够多的内存也会抛出OutOfMemoryError,虚拟机规范并没要求这个区域是否可扩展,但大部分实现的虚拟都是可扩展的.
本地方法栈与虚拟机栈基本一直,主要区别一个管理的目标是字节码定义的方法栈,本地方法栈管理的是本地方法.这个区域的规范并不强制语言与数据结构,给跨平台提供灵活的选择性.
这是虚拟机中最大的一块区域,也是开发者所指的JVM内存的主要含义,不仅仅是因为它所占比例一般最大,更重要的是垃圾回收的目标也主要在这个区域,这个区按照规范的定义是存放实例的唯一区域,但是随着最新的技术发展(JIT技术与逃逸)发展,对象也不一定在这个区域存放.这个区域也是多个线程共享的,因此也会带来多线程并发的复杂性.保持对象的一致性也是控制堆上的对象能够对多个线程保持数据的一致.堆上主要存放对象实例和数组,当JVM启动的时候就存在了堆区域,这与栈区不太一样,栈是在方法开始调用的时候才开始创建.堆中的垃圾回收根据对象生存周期的不同来进行分代收集,主要的收集算法也有标记-清除,复制-整理,复制-压缩等算法,根据这些算法也有一些串行,并行,甚至并发的垃圾收集器.在主流hotspot也分为年轻代(可以分为Eden和Survivor)和老年代.这个区域可以是连续的内存也可以只是逻辑连续的.
这个区域主要存放被JVM加载的类的信息,包括常量,静态变量,即时编译后的代码,这个区域垃圾回收效果收益不大但是是必要的.主要的收集对象为常量和需要卸载的类,类的回收机制有特定的要求.,并且十分的严苛.这个区域别名为Non-heap.在hotspot中使用堆中的永久区(其他JVM实现一般没所谓的永久区)来实现方法区,这样将垃圾回收器直接覆盖到了堆中的方法区(hotspot中的永久区),省去了专门为永久区进行垃圾收集的工作,但是这样增加了堆溢出的风险,因此现在jdk逐渐讲方法区移除到堆外.
属于方法区的一个部分,存放Class类编译器各种能够确定的字面量和符号引用(和直接引用).字节码格式中一般对其他区域要求严格,这个区域是比较宽松的.受到方法区大小限制,申请不到足够的内存也会抛出OutOfMemoryError异常.
有些时候我们可以直接使用堆外内存,例如jdk1.4引入的java nio就用channel与buffer调用本地方法在堆外分配内存,使用堆上的DirectByteBuffer作为引用进行操作,这样减少了堆内外数据交换,提升性能.当然这个区域的大小受到物理内存大小限制,申请不够抛出OutOfMemoryError异常.
新闻热点
疑难解答