少年帮|JVM内幕:Java虚拟机详解

这篇文章解释了Java 虚拟机(JVM)的内部架构 。 下图显示了遵守 Java SE 7 规范的典型的 JVM 核心内部组件 。
少年帮|JVM内幕:Java虚拟机详解上图显示的组件分两个章节解释 。 第一章讨论针对每个线程创建的组件 , 第二章节讨论了线程无关组件 。

  • 线程
    • JVM 系统线程
    • 每个线程相关的
    • 程序计数器
    • 本地栈
    • 栈限制
    • 栈帧
    • 局部变量数组
    • 操作数栈
    • 动态链接
  • 线程共享
    • 内存管理
    • 非堆内存
    • 即时编译
    • 方法区
    • 类文件结构
    • 类加载器
    • 更快的类加载
    • 方法区在哪里
    • 类加载器参考
    • 运行时常量池
    • 异常表
    • 符号表
    • Interned 字符串
线程这里所说的线程指程序执行过程中的一个线程实体 。 JVM 允许一个应用并发执行多个线程 。 Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系 。 当线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好以后 , 就会创建一个操作系统原生线程 。 Java 线程结束 , 原生线程随之被回收 。 操作系统负责调度所有线程 , 并把它们分配到任何可用的 CPU 上 。 当原生线程初始化完毕 , 就会调用 Java 线程的 run() 方法 。 run() 返回时 , 被处理未捕获异常 , 原生线程将确认由于它的结束是否要终止 JVM 进程(比如这个线程是最后一个非守护线程) 。 当线程结束时 , 会释放原生线程和 Java 线程的所有资源 。
JVM 系统线程如果使用 jconsole 或者其它调试器 , 你会看到很多线程在后台运行 。 这些后台线程与触发 public static void main(String[]) 函数的主线程以及主线程创建的其他线程一起运行 。 Hotspot JVM 后台运行的系统线程主要有下面几个:
虚拟机线程(VM thread)这个线程等待 JVM 到达安全点操作出现 。 这些操作必须要在独立的线程里执行 , 因为当堆修改无法进行时 , 线程都需要 JVM 位于安全点 。 这些操作的类型有:stop-the-world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除 。 周期性任务线程这线程负责定时器事件(也就是中断) , 用来调度周期性操作的执行 。 GC 线程这些线程支持 JVM 中不同的垃圾回收活动 。 编译器线程这些线程在运行时将字节码动态编译成本地平台相关的机器码 。 信号分发线程这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理 。
线程相关组件每个运行的线程都包含下面这些组件:
程序计数器(PC)PC 指当前指令(或操作码)的地址 , 本地指令除外 。 如果当前方法是 native 方法 , 那么PC 的值为 undefined 。 所有的 CPU 都有一个 PC , 典型状态下 , 每执行一条指令 PC 都会自增 , 因此 PC 存储了指向下一条要被执行的指令地址 。 JVM 用 PC 来跟踪指令执行的位置 , PC 将实际上是指向方法区(Method Area)的一个内存地址 。
栈(Stack)每个线程拥有自己的栈 , 栈包含每个方法执行的栈帧 。 栈是一个后进先出(LIFO)的数据结构 , 因此当前执行的方法在栈的顶部 。 每次方法调用时 , 一个新的栈帧创建并压栈到栈顶 。 当方法正常返回或抛出未捕获的异常时 , 栈帧就会出栈 。 除了栈帧的压栈和出栈 , 栈不能被直接操作 。 所以可以在堆上分配栈帧 , 并且不需要连续内存 。
Native栈并非所有的 JVM 实现都支持本地(native)方法 , 那些提供支持的 JVM 一般都会为每个线程创建本地方法栈 。 如果 JVM 用 C-linkage 模型实现 JNI(Java Native Invocation) , 那么本地栈就是一个 C 的栈 。 在这种情况下 , 本地方法栈的参数顺序、返回值和典型的 C 程序相同 。 本地方法一般来说可以(依赖 JVM 的实现)反过来调用 JVM 中的 Java 方法 。 这种 native 方法调用 Java 会发生在栈(一般是 Java 栈)上;线程将离开本地方法栈 , 并在 Java 栈上开辟一个新的栈帧 。