之前一直就很好奇 java -jar 到底发生了什么,为什么执行 java -jar 代码就自动运行了。今天我们来说明一下,尽量覆盖操作系统、编译原理、JVM 的一些东西。( 本文将处于一个不断更新的状态,知道上面这些东西覆盖的差不多了为止,如果可以的话,也会加上硬件方面的东西 ),主要的目的就是为了能以最简单的 java 代码来串一些相对来说比较底层的东西,让自己以及让每个读者对计算机能有一个相对全局的了解。
我们先约定如下:
1.操作系统仅仅指的是unix 或类unix
2. 64 位机器
3. 64位 jDK
我们把下面这个类,打成一个 jar 包然后执行。
/**
* Created by shengjk1 on 2020/8/30.
*/
public class Test {
public static int b;
private int a;
public static void main(String[] args) {
Test test = new Test();
test.test();
System.out.println("b = " + b);
System.out.println("执行完毕");
}
public void test() {
byte i = 15;
int j = 8;
int k = i + j;
}
}
学过 java 的同学应该都知道这个 Test 类的每行代码都是干嘛的,就不一一解释了。
首先会编译成 class 文件
下面开始执行
JVM 的准备工作完成之后,JVM会调用我们的 main() 方法,可是内存里面并没有 main 方法,这就是所说的 页面故障,操作系统会从磁盘上读取相应的指令。也就进入了 JVM 的类加载。
类加载得有加载器
要加载 main() 方法所在的 Test 类,会首先判断有没有没有加载的父类,若有未加载的父类则会先加载其父类。在这里我们的 Test 类并没有明确的父类 ,JVM就把 Test 类加载到 JVM 的内存中形成一个 java.lang.Class 对象
而对象在JVM 中的内存布局如下:
所以说未压缩的情况下 class 对象至少占用 8 byte( 32 位 JVM ) 16byte ( 64 位 JVM )
这个过程中,会把类的版本、字段、方法、等描述信息以及代码缓存放入 Metaspace,把常量池表中的各种字面常量符号引用等放入方法区的运行时常量池。
同时会对 class 文件进行验证,包括文件格式、元数据等,以保证 class 文件不危害虚拟机自身的安全。
加载验证结束后,开始进入准备阶段,主要做两件事情
准备阶段完成之后,开始解析,主要做一件事
凡是在此阶段可以解析的方法引用都成为静态解析,调用的时候就叫静态调用
静态解析一般都是静态方法和私有方法,并且在运行期间是不变的
我更喜欢类的初始化,因为我们调用了 main() 方法,实际上是 静态调用 invokestatic 。
类初始化的几种情况:
当然了类初始化完了之后如果需要会进行对象的初始化,调用对象的构造器 () ,调用之前会先调用父类的。
执行 main 方法也就需要方法调用,对于方法调用 JVM 是通过几条指令来实现的
只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在类加载的解析阶段转化为直接引用 ( 静态方法、私有方法、实例构造器、父类方法( super. )、被final 修饰的方法),对应的方法称为非虚方法,其他的都是虚方法 ( 在运行期间根据实际类型确定方法执行版本 )。
虚方法主要揭示了 java 多态的一些特征,像多态、方法重写。
我们都知道方法是在栈中执行的,方法的执行过程其实就是不断的出栈入栈的过程
我们以 test() 方法为例来具体分析一下
0: bipush 将 15 放入栈中
2: istore_1 将栈顶元素方入局部变量表第 1 个位置
3: bipush 将 8 放入栈中
5: istore_2 将栈顶元素方入局部变量表第 2 个位置
6: iload_1 将局部变量表的第 1 个位置元素放入栈
7: iload_2 将局部变量表的第 2 个位置元素放入栈
8: iadd 相加
9: istore_3 将栈顶元素(也就是相加的结果)方入局部变量表第 3 个位置
===================================================
===================================================
===================================================
===================================================
6,7 一起
===================================================
===================================================
打印输出会从用户态进入内核态,操作系统会调用 IO 操作输出相应的结果。
发生系统调用,JVM 退出
2. 操作系统的进程空间可分为用户空间和内核空间,它们需要不同的执行权限。其中系统调用运行在内核空间。
3.常见系统调用