JVM的主要结构以下图所示,图片引用自舒の随想日记。 html
方法区和堆由全部线程共享,其余区域都是线程私有的 java
相似于PC寄存器,是一块较小的内存区域,经过程序计数器中的值寻找要执行的指令的字节码,因为多线程间切换时要恢复每个线程的当前执行位置,因此每一个线程都有本身的程序计算器。这一个区域不会有OutOfMemeryError。当执行Java方法时,这里存储的执行的指令的地址,若是执行的是本地方法,这里的值是Undefined。 算法
虚拟机栈也是线程私有的,每建立一个线程,虚拟机就会为这个线程建立一个虚拟机栈,虚拟机栈表示Java方法执行的内存模型,每调用一个方法,就会生成一个栈帧(Stack Frame)用于存储方法的本地变量表、操做栈、方法出口等信息,当这个方法执行完后,就会弹出相应的栈帧。 数组
若是请求的栈的深度过大,虚拟机可能会抛出StackOverflowError异常,若是虚拟机的实现中容许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出OutOfMemoryError异常。 多线程
栈帧分为三部分:局部变量区(Local Variables)、操做数栈(Operand Stack)和帧数据区(Frame Data)。 函数
局部变量区被组织一个一个从0开始的字数组,byte、short、char在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true,long和double占据两个字长。 布局
操做数栈也被组织为一个字数组,但不一样于局部变量区,它不是经过数组下标访问的,而是能过栈的Push和Pop操做,前一个操做Push进的数据能够被下一个操做Pop出来使用。 spa
这部分的做用主要有三部分: .net
与虚拟机栈相似,只是是执行本地方法时使用的。 线程
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码等信息。
方法区是线程间共享的,当两个线程同时须要加载一个类型时,只有一个类会请求ClassLoader加载,另外一个线程会等待。
对于每个加载的类型,会在方法区中保存如下信息:
对于每个字段,会在方法区中保存如下信息(字段声明顺序也会保存):
对于每个方法,会在方法区中保存如下信息(方法声明顺序也会保存):
若是方法不是抽象方法并非本地方法(Native Method),还会保存如下信息:
虚拟机须要存储一些数据,用来快速地访问一个类对象中的方法,通常实现为一个方法表。
方法区中还有一部分是运行时常量池,主要用来存储编译时生成的字面量和符号引用,常量也能够在运行时产生,如String的intern方法。
方法区中也可能存在GC,但虚拟机规范对此不作要求,主要是回收一些常量和卸载一些不用的类型信息,不过要卸载一个类的条件很难达到,并且些处GC其实也回收不了多少内存。
虚拟机中用于存放对象与数组实例的地方,垃圾回收的主要区域就是这里(还可能有方法区)。
若是垃圾收集算法采用按代收集(目前大都是这样),这部分还能够细分为新生代和老年代。
新生代又可能分为Eden区,From Survivor区和To Survivor区,主要是为了垃圾回收。全部的线程共享Java堆,在这里还能够划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。
Java堆只要求逻辑上是连续的,在物理空间上能够不连续。
JDK1.4中引用了NIO,并引用了Channel与Buffer,可使用Native函数库直接分配堆外内存,并经过一个存储在Java堆里面的DirectByteBuffer对象做为这块内存的引用进行操做。
当新建一个对象时,会在堆中为这个对象分配内存,并在栈中有一个对这个对象引用,除此以外,在Java堆中还要能经过这个对象找到它的类型信息(对象类型,父类,实现的接口,包含的字段与方法等)。
Reference在Java虚拟机中定义为指向对象的引用,但没有定义这个Reference应该有怎么实现。
一种实现是Reference直接存储对象在堆内的地址,对象的类型信息能够在对象在堆中的内存布局中存储,如存储在对象内存的开头等。
另外一种实现是Reference指向一个句柄表中的一个位置,句柄中保存了对象的实际位置及它对应的类型信息。
使用句柄的好处是当在内存中移动对象的位置时,只须要更新句柄表中的内容,不须要改变引用值,但会多一次内存访问开销,直接引用的优缺点与此相反。
参考资料: