概述
对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”,又是从事最基础工作的劳动人民——既拥有每一个对象的“所有权”,又担负着每一个对象生命从开始到终结的维护责任。
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序员把控制内存的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。
运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,
- 程序计数器
- java 虚拟机栈
- 本地方法栈
- java 堆
- 方法区
- 运行时常量池
- 直接内存
程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
理解:
CPU中的程序计数器(PC)
CPU中的PC是一个大小为一个字的存储设备(寄存器),在任何时候,PC中存储的都是内存地址(是不是有点像指针?),然后CPU根据PC中的内存地址,到相应的内存取出指令然后执行,并且在更新PC的值。在计算机通电后这个过程会一直不断的反复进行。计算机的核心也在于此。-
JAVA运行时数据区域程序计数器
在CPU中PC是一个物理设备,而java中PC则是一个一块比较小的内存空间,它是当前线程字节码执行的行号指示器。在java的概念模型中,字节码解释器就是通过改变这个计数器中的值来选取下一条执行的字节码指令的,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等功能都依赖于这个计数器。因为我们程序运行过程中只是改变计数器中的值,而不会随着程序的运行需要更大的空间,也就不会发生溢出情况。
特点:
- 线程隔离,每个线程工作室都有属于自己的独立计数器
- 执行 Java 方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的地址
- 执行本地方法时,程序计数器的值为空(Undefined),因为 native 方法时 java 通过 JNI(Java 本地接口)直接调用本地的 C/C++ 库,由于此方法是通过 C/C++ 实现的,无法生成字节码文件,所以其在执行时内存的分配不是由 JVM 决定的
- 程序计数器占用的内存很小,在进行 JVM 内存计算时,可以忽略
- 另外,JVM 中只有程序计数器没有规定任何 OutOfMemoryError
解释:
TestProgramCounter.java
public class TestProgramCounter {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = getAdd(a, b);
System.out.println(c);
}
private static int getAdd(int a, int b) {
return a + b;
}
}
javac TestProgramCounter.java //编译文件
javap -c TestProgramCounter.class //查看编译后的class文件中数据格式
Compiled from "TestProgramCounter.java"
public class TestProgramCounter {
public TestProgramCounter();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: invokestatic #2 // Method getAdd:(II)I
9: istore_3
10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_3
14: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
17: return
}
Java虚拟机栈
参考自:
《深入理解Java虚拟机》(第3版) 周志明
https://segmentfault.com/a/1190000024447450