Java内存区域
Java内存区域
Java的内存区域可以按照线程是否共享而分为两大块:
- 线程私有:程序计数器、虚拟机栈、本地方法栈。
- 线程共享:堆、方法区。
程序计数器
程序计数器主要是用来指示当前正在执行的字节码行号,字节码解释器工作时就是通过改变这个计数器的值来选取下一条要执行的字节码指令,流程控制就是依赖计数器来完成的。
特点
- 线程私有,每个线程的程序计数器指向各自线程正在执行的字节码地址。
- 当执行Java方法,计数器记录的是当前正在执行的字节码指令的地址;当执行native方法时,这个计数器的值为空(Undefined)。
异常
- 在虚拟机规范中唯一一个没有任何
OutOfMemory
情况的区域。
虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧,用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行结束对应一个栈帧在虚拟机栈的入栈到出栈的过程。
特点
- 线程私有。
- 生命周期与线程相同。
异常
- 当线程请求的深度大于虚拟机允许的深度时,会抛出
StackOverflowError
。 - 如果虚拟机允许动态扩展,扩展时无法申请到足够的内存,就会抛出
OutOfMemoryError
。
本地方法栈
本地方法栈与虚拟机栈类似,本地方法栈是为本地方法服务的。Hotspot虚拟机将本地方法栈和虚拟机栈合二为一。
异常
同虚拟机栈。
堆
堆是内存管理中最大的一块。唯一目的是用来存放对象和数组,是垃圾回收器管理的主要区域。
特点
- 线程共享。
- 物理上可以处于不连续的内存空间中。
- 可以固定大小,也可以扩展,通过
-Xmx
-Xms
控制。
异常
- 当没有内存完成实例的分配,并且堆无法再扩展时,抛出
OutOfMemoryError
。
本地线程分配缓冲 TLAB
因为对象的创建是十分频繁的,为了保证线程安全,每个线程都在堆中预先分配一小块内存作为缓冲,那个线程要分配内存就在那个线程的TLAB上分配,只有在TLAB用完的时候并分配新的TLAB,
才需要同步锁定(HotSpot使用CAS)。可以通过 -XX:+/-UserTLAB
指令来设置是否使用TLAB。
方法区
用来储存被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在Hotspot
中使用永久代来实现方法区。以此来实现对方法区的内存管理。可以用-XX:MaxPermSize
来实现设置永久代的上限。
异常
- 无法分配内存时,
OutOfMemoryError
。
运行时常量池
运行时常量池是方法区的一部分,用于存储编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的常量池中存放。
异常
- 同方法区。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分也被频繁的使用。在NIO
中,可以使用native函数库分配堆外内存,然后通过储存在堆上的 DirectByteBuffer
对象作为对这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回赋值数据。
异常
- 直接内存不会受到Java堆大小的限制,但是如果总内存大于物理内存限制时们就会导致
OutOfMemoryError
。