一、运行时区域数组
Java虚拟机在执行Java程序的时候会把它管理的内厝划分为若干个不一样功能的数据区域,如图所示多线程

- 首先是程序计数器,程序计数器能够理解为当前程序执行的字节码的行号指示器,计数器中的数据便是下一条将要执行的字节码指令的行号。由于Java虚拟机的多线程是经过轮流切换并分配处理器执行时间的方式来实现的,在任意一个时刻,一个处理器(单核)或是一个核(多核)都只会执行一个线程中的指令,因此,每一个线程都拥有本身的程序计数器,还有就是若是执行的是一个Java方法,那么计数器中记录的就是字节码的地址,若是执行的是Native方法,那么计数器的值则是空(UnderFined)
- 虚拟机栈,虚拟机栈描述的是Java方法执行的内存模型,每次方法在执行的时候都会建立一个栈帧,栈帧中存储了局部变量表、操做数栈、动态连接、方法出口等等信息。每个方法的从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈出栈的过程。若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常,若是栈能够动态扩展,且扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常。局部变量表存放了编译器可知的各类基本数据类型(boolean、byte、char、short、float、long、double)、对象引用(reference类型,它多是一个指向对象起始地址的指针,也多是一个表明对象的句柄或是其余于此对象相关的位置)和returnAddressl类型(指向了一条字节码指令的地址)
- 本地方法栈和虚拟机栈的做用是相似的,区别就是本地方法栈是为Native方法服务的,一样也会抛出StackOverflowError异常和OutOfMemoryError异常。
- Java堆,Java堆首先是全部的线程共享的一块内存区域,这个区域的惟一目的就是用于存放对象实例,几乎全部的对象实例都在这里分配内存。若是堆中没有足够的内存完成实例分配,而且对也没法再扩展,将会抛出OutOfMemoryError异常。
- 方法区,这也是全部的线程共享的一块内存区域,它用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据,当方法区没法知足内存分配需求时,将会抛出OutOfMemoryError异常。
- 运行时常量池,这是方法区的一部分,这部分专门用于存放编译期产生的各类字面量和符号引用,这部分将在类被加载后进入方法区的常量池中存放,和方法区同样没法知足内存分配需求时,将会抛出OutOfMemoryError异常。
- 直接内存,这部分并非虚拟机运行时数据区的一部分,但也被频繁使用。这部分出现的是由于Native函数本身建立的,就好比NIO类读取文件的时候,会直接分配堆外内存,这一部份内存就是直接内存,直接内存区域动态扩展时出现内存没法知足时会抛出OutOfMemoryError异常。
二、对象的建立函数
一般建立一个对象实例是使用的new关键字,当虚拟机遇到new指令的时候,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用表明的类是否已经被加载、解析和初始化过,若是有,那么将先执行响应的类加载过程。布局
类加载检查经过后,虚拟机将这个新生对象在堆中分配内存,所须要的内存大小将在类加载完成后肯定,假设Java堆中的内存是规整的,也就是说空闲的在一边,非空闲的在一边,当分配内存的时候只须要指向中间边界的指针向空闲那边移动,这种方式称做指针碰撞,若是是非规整的,空闲的和非空闲的互相交错着,那么将有一个列表记录着那部分是空闲的,这样分配的时候就须要从这个列表中找到合适的空闲区域,并更新表中的记录,这种方式叫做空闲列表。spa
内存分配好以后,虚拟机将对分配的内存空间都初始化为零值,初始化完成后,虚拟机将对对象进行必要的设置,例如对象是属于那个类的实例、怎么才能找到类的元数据信息,对象的哈希码、对象的GC分代年龄。等设置结束后,虚拟机就已经建立好了一个新的对象,接下来在执行初始化方法init方法,这样一个完整的对象就算彻底生成了。线程
三、对象的内存布局指针
对象在内存中存储的布局分为3块区域:对象头、实例数据和对齐补充code
- 对象 头包括两部分,第一部分用于存储对象自身运行时的数据,例如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例。另外若是对象是一个Java数组,那么在对象头数组中还必须有存放记录数组长度的数据。
- 实例数据部分是对象真正存储的有效信息,就是程序中定义的各类类型的字段内容。不管是从父类继承下来的,仍是在子类中定义的,都须要记录下来
- 对齐补充这一部分 不必定存在也没有什么特殊的含义,仅仅起到占位符的做用,由于有的虚拟机的自动内存管理系统要求对象的起始地址必须是8字节整数倍,所以须要对象的大小也必须是8字节的整数倍。
四、对象的访问定位对象
使用对象须要找到对象在堆中的地址,一般定位一个对象的地址主流的方式有使用句柄和直接指针两种blog
- 使用句柄,Java堆中会专门划分出一块内存来做为句柄池,栈上的reference数据中存储的就是句柄的地址,而句柄中包含了对象实例数据与类型数据各自的地址信息。
- 直接指针将直接指向Java堆中的Java实例数据的地址,Java堆中的实例数据也将有一个指向对象类型数据的指针。

这两种方法各有各的好处,句柄方式中reference中存储的句柄地址是一直不变的,当对象被移动的时候(在垃圾处理的时候对象被移动是很广泛的行为)时只会改变句柄中的实例数据指针。
而直接指针的好处就是速度更快,当你要操做一个对象的时候,句柄方式须要先定位到句柄在定位到对象实例,直接指节省了一次指针定位的开销。