程序计数器
作用:存储下一条JVM指令的执行地址(当前线程所执行的字节码的行号指示器)
java代码在编译过程中,会被转化为JVM指令(二进制字节码)。二进制字节码通过字节码解释器解释为机器码,才能经CPU处理。这个过程中,程序计数器时刻指向下一条JVM指令地址。
特点:
- 线程私有:每条线程具有独立的程序计数器,各条线程之间程序计数器互不影响,独立存储。
- 唯一一个不存在内存溢出(
OutOfMemoryError
)的区域
虚拟机栈
虚拟机栈:每个线程运行时所需要的内存空间
栈帧:方法调用时所需要的内存空间
每个栈由多个栈帧组成,对应着每次调用方法时所占用的内存。每个线程只能有一个活动栈帧,对应着当前正在执行的方法。
两种异常状况
StackOverflowError
如果线程请求的栈深度大于虚拟机所允许的深度,抛出
StackOverflowError
异常。如栈帧过多或过大。
OutOfMemoryError
虚拟机栈可以申请动态扩展。当扩展时无法申请到足够内存时,抛出
OutOfMemoryError
异常
特点:
- 栈是线程私有的
- 垃圾回收不涉及栈内存
- 当扩展时无法申请到足够内存时,抛出
OutOfMemoryError
异常
本地方法栈
作用同虚拟机栈。
不同点在于:本地方法栈为虚拟机使用到的 Native 方法服务;虚拟机栈为虚拟机执行 Java 方法(字节码)服务。
也会抛出StackOverflowError
和OutOfMemoryError
异常。
Java堆
Java虚拟机管理的内存最大的一块区域,用于存放对象实例,在虚拟机启动时创建。
特点:
- Java堆是被所有线程共享的内存区域。
- 存在垃圾回收机制。
- 当堆中没有内存完成实例分配,并且堆无法扩展时,抛出
OutOfMemoryError
异常。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。在实现时,可实现成固定大小,也可实现堆空间扩展(通过 -Xmx 和 -Xms 控制)。
方法区
用于存储已被虚拟机加载的类信息、常量(运行时常量池)、静态变量等数据。
特点:
- 方法区是线程共享的内存区域
- 可以选择不实现垃圾回收
- 当方法区无法满足内存分配需求时,抛出
OutOfMemoryError
异常
Java虚拟机规范规定:方法区不需要连续的内存;可以选择固定大小或可扩展;可以选择不实现垃圾回收。
方法区垃圾回收目标主要是针对常量池的回收和对类型的卸载
运行时常量池
常量池:**用于存放编译期生成的各种字面量和符号引用。**虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
运行时常量池:
常量池是*.class
文件中的,当该类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
运行时常量池具有动态性。常量并不一定只在编译期间产生,运行期间也可能将新的常量放入池中。常见例子:intern()
方法
运行时常量池是方法区的一部分,与方法区相同,可以抛出OutOfMemoryError
异常
直接内存
- 常用于NIO操作,用于数据缓冲区
- 分配和回收成本高,但读写较快
- 不受垃圾回收机制管理
直接内存并不是虚拟机定义的内存区域,但是这部分内存也会被频繁使用,并且动态扩展时可能抛出OutOfMemoryError
异常
在IO的过程中,Java无法直接操作磁盘文件,而是使用本地方法操作和读取磁盘文件。这个过程是在系统内存中创建缓冲区,将数据读取到系统缓冲区,随后将缓冲区数据拷贝至堆内存中。
JDK1.4引入了一种基于通道和缓冲区的IO方式(NIO)之后,可以使用Native函数库直接配堆外内存,然后通过DirectByteBuffer
对象操作这块内存。避免了在Java堆和Native堆中来回复制数据,可以显著提升性能