JVM拾遗(1): 万米高空看JVM

开个新系列,本系列会对JVM的知识点拾遗, 也是对自己JVM学习的一个梳理。
关于JVM的方方面面都会讲到,应该会是理论为主,辅代码和图(不会是纯理论的)。

以前看过林林总总的JVM书籍,比如周志明大佬的<<深入理解Java虚拟机>>和张秀宏大佬的<<自己动手写Java虚拟机>>, 也跟着造了个小JVM,所以也尝试将知识整理下
本系列会比较长,但是会把知识点细分,前后贯通串起来。
如果没有特殊指出,JVM使用的是Hotspot VM,JDK使用的是OpenJDK8

1. JVM是什么

JVM的全称是Java Virtual Machine,也就是Java虚拟机, 1995年Java发布1.0的时候,提出了一个Write Once, Run Anywhere的口号,所以要保证Java的平台
无关性。然而物理机只认机器码, 有伟人说过: 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决, 既然物理机只认机器码,那么我们加一层抽象,这层抽象和平台有关
,然后再其上定义自己的统一格式,不就达到目的了吗。

没错,这层抽象就是JVM, 这个平台无关的”代码指令“就是class文件.

2. 内存管理

JVM是一个带有GC(Garbage Collect)的虚拟机, 和传统的c/c++不一样,对象的创建和释放不需要开发者手动操作,不容易出现内存泄漏和溢出的问题。
开发者不太用关心的事情,就交给JVM了。然而软件开发没有银弹,排查内存泄漏的时候,如果不了解一些gc的细节,将会很艰难。后面会详细聊gc有关的知识。

JVM内存区域结构

JVM8规范里将内存区域划分为

  • PC寄存器
  • JVM栈
  • 方法区
  • 运行时常量池
  • 本地方法栈

如果是学过CSAPP的老司机, 看到PC寄存器就立马知道这是干什么的,JVM模拟的是真实的物理机架构,class文件内容就是机器指令, JVM就是CPU和内存,PC寄存器放的是当前线程执行到的”代码位置”

JVM栈是用来模拟计算机的寄存器结构的,寄存器是CPU的运算单元,多个种类的寄存器配合一起工作才能完成计算. 比如某x86架构的汇编指令

1
movq %rax, (%rbx)

表示把寄存器rbx的地址保存到rax寄存器中,但是汇编指令是和硬件相关的,所以不方便移植. 反而是基于虚拟栈的架构, 可移植性比较强。JVM采用的就是这种方式。栈里面是栈帧,栈帧里有局部变量表和操作数,动态链接等信息.

JVM栈是线程私有的,每个线程有自己的栈.

也是Javaer耳熟能详的内存区域了, new出来的对象,就是在这里分配的,是被所有线程所共享的那部分区域, 这里也是gc主要发生的区域。

方法区也是各个线程共享的区域,用于存储已经被JVM加载的类,常量,JIT后的代码等数据。几乎不会发生gc.

运行时常量池方法区的一部分, 常量池(Constant Pool)一般是在class文件里的,class的常量池存放了一个类的各种字段长度方法名字之类的信息, 学术点讲叫符号引用字面量.

本地方法栈是为c/c++等传统技术栈准备的,对于java来讲,c就是”本地”方法,因为很早以前java是靠c来解释执行的, 类似于寄生在c上, 现在jvm也是用c++实现的。

一张图说明问题

3. 大好Hello World如何执行

回过头来举个现实点的例子来整体把握JVM,如我们常用的HelloWorld.java,

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

首先,我们需要把HelloWorld.java编译成HelloWorld.class,然后就可以运行了

1
2
3
$ javac HelloWorld.java
$ java HelloWorld
Hello World

main方法是JVM预留的程序执行入口,JVM启动之后会先装载自己的类, 如java.lang.Object, 然后再加载我们自定义的HelloWorld类
加载完成之后,方法区里就有HelloWorld类的字节码了,字节码里有我们main方法的代码,我们可以用javap来反编译class文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// javap -c HelloWorld
public class HelloWorld {
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}

字节码的含义这里不多讲,后面会细讲。
执行的过程就是jvm会从上到下依次执行指令,直到所有的指令执行完毕,虚拟机退出。

4. 总结

本篇从什么是JVM开始,介绍了Java虚拟机的由来: 用于做平台无关性的抽象的,同时还介绍了自动内存管理机制里的gc和内存区域划分,从整体上把握JVM, 最后通过一个HelloWorld的小例子提升对JVM的感性认识。