本章主要介绍了Java的内存区域以及会触发对应区域内存溢出的触发条件及表现结果。同时还对java对象的内存布局进行了简单的讲解。
2.2 运行时数据区域:
比较直观的一张图来表示:
然后分别介绍了各个区域存储的主要对象及工作原理:
2.2.1 程序计数器
概念:程序计数器又叫PC或者PCG(Program Counter Register),是一块很小的内存空间,它可以看作是当前线程所执行的字节码行号的指示器。在虚拟机的概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都是依赖这个计数器来完成的。
2.2.2 Java虚拟机栈(JVM Stacks)
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程是相同的。虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法的调用与完成的过程,就对应着一个栈帧在虚拟机栈中的入栈到出栈的过程。
在Java虚拟机闺房中,做了如下规定:当线程请求的栈深度大于虚拟机所允许的深度,则会抛出StackOverFlowError异常;而当虚拟机栈可以动态扩展,并且当扩展时无法申请到足够的内存时,就会抛出OutOfMemeryError异常。
2.2.4 Java堆
对于大多数应用来说,Java堆(Java heap)是Java虚拟机所管理的内存中最大的一块。Java堆内存在虚拟时启动时创建,这块内存区域被所有线程共享。此内存区域的唯一目的就是存放对象示例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
2.2.5 方法区
方法区和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2.2.7 直接内存(direct memory)
在JDK1.4中新进入了NIO类,引入了一招能够基于通道(channel)与缓冲区(Buffer)的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。如果我没记错的话,Netty中NIO是虚饮用的一种应用场景。
2.3.2 对象的内存模型
在Hotspot虚拟机中,对象在内存中的存储布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
1.对象头
对象头包含两部分信息:
1.1 Mark Word
主要用于存储以下信息:如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。在Hotspot中的实现见下图:
1.2 类型指针
即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个Java数据,那在对象头中还必须有一块用于记录数组长度的数据。