Java虚拟机内存管理(1)
运行时数据区域
Java虚拟机所管理的内存包括以下几个运行时数据区域。如下图所示
[2] 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。每条线程都有一个独立的程序计数器,线程之间的计数器互不影响,独立存储,即为“线程私有”的内存。
如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是Nation方法(简单来说就是非Java代码的接口),则计数器值为空。
[3] Java虚拟机栈
也是线程私有内存,描述的是Java方法执行的内存模型,每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用和执行完成,对应在虚拟机栈中就是栈帧的进栈和出栈。
常说的栈内存,指虚拟机栈中局部变量表部分,此部分存放着编译期可知各种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。
在JVM规范中,对这个区域规定了两种异常情况。
① StackOverflowError异常:线程请求的栈深度大于虚拟机所允许的深度。
② OutOfMemoryError异常:当虚拟机可以动态扩展,扩展时无法申请到足够的内存。
[4] 本地方法栈
与虚拟机栈类似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机使用到的Nation方法服务。Sun HotSpot虚拟机将这两种合二为一
[5] Java堆
在大多数应用中,Java堆(Java Heap)是JVM所管理的内存中最大的一块。是所有线程共享的区域。Java堆用于存放对象实例。因为是垃圾收集器管理的的主要区域,所以也叫“GC堆”。
当堆中没有内存完成实例分配,并且堆也无法再扩展时,会抛出OutOfMemoryError异常。
[6] 方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。是各个线程共享的内存区域。也叫“非堆”(Non-Heap)。
当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
[7] 运行时常量池
是方法区的一部分,用于类加载后存放class文件中的常量池信息(用于存放编译期生成的各种字面量和符合引用)。
当常量池无法再申请到内存时,抛出OutOfMemoryError异常
[8] HotSpot虚拟机中对象的创建过程(普通Java对象)
虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,则执行相应的类加载过程。检查通过后,则为对象分配内存(对象所需内存的大小在类加载完成后确定),分配内存即在Java堆中划分一块区域出来,有“指针碰撞”和“空闲列表两种方式”,使用哪种方式由Java堆是否规整决定。内存分配完成后,虚拟机便将分配到的内存空间初始化为零,再对对象进行必要的设置,如设置对象所实例化的类、对象的哈希码等。至此,一个新的对象已经产生。
但执行完new指令后会接着执行<init>方法,将对象按程序员的意愿进行初始化后,一个真正可用的对象才算完全产生出来。
[9] 对象的内存布局
HotSpot虚拟机中,对象在内存的存储的布局可分为3块区域:对象头、实例数据和对齐填充。
对象头包括两部分信息,第一部分用于存储对象自身的运行时数据。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据是对象真正存储的有效信息,即程序代码中所定义的各种类型的字段内容。
对齐填充,这个不是必然存在的,仅仅起占位符的作用。因为Hotspot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍。当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
[10]对象的访问定位
有两种方法,如下图所示。