1、程序计数器
程序计数器是一块较小的内存,它可以当作当前线程所执行的字节码的指示器,程序计数器是程序的控制流。程序的循环,跳转,异常,线程恢复等都由它控制。程序计数器它是线程私有的。它是Java虚拟机规范中唯一没有规定任何OutOfMemoryError的情况,其生命周期也和线程一样。
2、Java 虚拟机栈
Java虚拟机栈也是线程私有的,它的生命周期和线程一样。其描叙的是Java方法的线程内存模型,每个方法被执行时Java虚拟机栈都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接方法出口等信息,每一个方法执行完成就代表一个栈帧从Java虚拟机从入栈到出栈的完成。
- 局部变量表:存放这编译器可知的各种Java虚拟机基本数据类型、引用类型和 returnAddress 类型(指向了一条字节码指令的地址)。所以局部变量表所需的内存空间在编译器就确定了,当方法入栈的时在栈帧中分配多少局部变量的空间是完全确定的。
对于 Java 虚拟机栈来说如果线程请求的深度大于虚拟机栈所允许的深度,则会抛出StackOverflowError 异常。如果 Java 虚拟机栈容量可以动态扩展,当栈无法扩展时无法申请到足够的内存则会抛出OutOfMemoryError。(HotSpot 虚拟机的栈容量时不可以动态扩展的)
3、本地方法栈
本地方法栈和Java虚拟机栈有点类似,只是Java虚拟机栈是为执行Java方法提供服务而本地方法栈为本地方法提供服务,其也会存在OutOfMemoryError 和 StackOverflowError异常
4、Java堆
Java堆是Java虚拟机占用内存最大的一部分,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的内存,因此也叫GC堆。Java堆可以处于物理上不连续的空间中,但在逻辑上他应该被视为连续的。Java堆可以被实现为固定大小,也可以扩容(通过参数 -Xmx 和 -Xms设定)。如果没有内存来完成实例分配时,且堆也无法再次扩容时则会抛出OutOfMemoryError。Java 堆属于线程共享的。
5、方法区
方法区和Java堆一样是线程共享的。它用于存储已被Java虚拟机加载的类型信息,常量,静态变量。方法区中包含了运行时常量池。运行时常量池用于存放编译期生成的各种字面量与符号的引用,Java 并不要求常量一定在编译期才能产生,运行期间也是可以产生的,其也存在OutOfMemoryError异常。
HotSpot 对象的创建
Java是一门面向对象的语言,在Java程序运行的过程中无时无刻有对象被创建出来而。但Java虚拟机遇到一个new指令时
- 其会先去检查这个指令参数能否在常量池中定位到一个类的符号应用,并检查其是否有被加载、解析和初始化的过。如果没有则先去执行相应的类加载过程。
- 在完成类加载过程之后虚拟机将会为其分配内存,内存的大小其会在类加载的过程确定。为Java对象分配内存时有两中情况,一:如果Java堆中的内存时绝对完整的则会使用指针碰撞的方式分配(已使用内存和未使用内存分开,中间用一个指针隔离),二:如果内存是不连续的则无法使用指针碰撞的方式分配内存,这时虚拟机就必须要维护一个列表来记录那些内存块是可以使用的,在分配时找出一块足够大的空间分配给对象实例。在内存分配完成之后其会对其内存空间进行初始化。
- 由于创建对象在虚拟机中是非常频繁的行为,在并发的情况下其实线程不安全的可能会出现A分配内存指针还没来得及修改对象B又同时使用原来的指针来分配内存了。解决这问题有两种方案,一:对内存分配空间的动作采用同步,虚拟机采用的是CAS(Compare and Swap)操作。二:把内存分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存(称为本地线程分配缓冲),哪个线程要分配空间就在哪个本地缓冲中分配,只有当本地缓冲区分配完了,分配新的缓冲区时才采用同步锁定。
- 在完成内存分配之后,Java虚拟机还要对对象进行必要的设置如:该对象属于那个类实例、对象的哈希码、对象GC的分代年龄、是否偏向锁等信息。
- 现在从虚拟机的角度来看一个新的对象已经产生了。但从Java的角度来看对象的创建才刚刚开始 ---- 构造函数。只有执行完Java都构造函数之后才代表一个对象真正的创建出来。
实例对象的布局,在HotSpot虚拟机中,对象堆内存的存储布局可以划分为三个部分:对象头、实例数据和对齐填充。