本文是对过往笔记的一些整理,如有不对,欢迎指导。
我们平时写的代码都是以 .java 为后缀结尾的文件。而我们部署应用时,要么是打成 jar 包, 通过
java -jar
的命令运行;要么打成 war 包, 直接丢到 tomcat 等 web 容器中,然后启动 web 容器。这样我们的 Java 应用就算是跑起来了。
首先想到就是编译源文件得到 .class 结尾的字节码文件
然后就是把这些字节码文件打成 jar 包
紧接着使用 java 命令运行 jar 包,此时就会启动一个 jvm 进程
最后 jvm 进程会负责管理运行字节码文件
在上一段末尾,有提到 jvm 会接手字节码文件,保证代码得以运行使用。那么 jvm 是如何做的呢?
这时就需要引入一个概念 - JVM 的类加载机制。这也是一个常见的面试题。
先来笼统的说下一个类从加载到使用所经历的过程
其中,验证,准备,解析 合在一起称为 连接阶段
下面会针对其中几个重要阶段逐个进行说明
问:JVM 什么时候会去加载一个类?
答:当然是,用到的时候。
JVM 进程启动后,一定会先加载指定的主入口类,然后从 main 方法开始执行。
比如下面的代码,当 App.class 被加载到内存后,随即开始执行 main 函数时,此时需要实例化 User 类,所以这个 User 类也会被加载到内存中去。
public class App {
public static void main(String[] args) {
User user = new User();
}
}
讲到这里就大概知道了,所谓加载过程就是将对应的字节码文件加载到内存中去。
确保 加载的类信息 符合 Java 虚拟机规范,避免产生安全性问题。
所谓这个规范,简单来讲就跟英语语法一样,语法用不对,你想表达的语义别人就听不明白。万一产生歧义,会被打的。
为类变量分配内存并设置初始值
当 App.class 被加载到内存中后,紧接着验证内容符合规范后,就需要给 App 中的类变量分配内存空间。
比如下面代码中的 num 变量,在准备阶段,他就会被赋一个初始值 0
public class App {
// 定义一个类变量
public static int num;
public static void main(String[] args) {
// User user = new User();
}
}
把类的二进制数据中的符号引用替换为直接引用。这个确实听起来比较复杂,涉及到一些底层的相关知识。先不用细究。
有句话说的好,可以xx,但是没必要!!!
对于以下代码,前面 ‘准备’ 阶段有说到,会给 num 赋一个初始值 0,
public class App {
// 定义一个类变量
public static int num;
public static void main(String[] args) {
// User user = new User();
}
}
那么对于如下代码,通过调用一个工具类求得的和什么时候会赋值给 num 这个类变量呢?
结论就是 初始化 阶段,另外静态代码块也是在这个阶段执行的。
public class App {
// 定义一个类变量
public static int num = NumberUtil.sum(1, 2);
public static void main(String[] args) {
// User user = new User();
}
}
上面描述的一些列过程都是由一个叫 类加载器 的东西来实现的。
而类加载器又分为以下几种:
负责加载 jdk 安装目录下面的 lib 目录下的核心类库。
在 lib 目录下,还有一个 ext 目录,这里面有些类就需要这个类加载器来加载。
我们在配置 java 环境变量时,一般会配一个 ClassPath,这个ClassPath 所指的路径里面类是由该类加载器加载的。
最后一种就是自定义类加载器,我们可以根据需要去决定是否自定义这样一个类加载器。
双亲委派模型
当你的应用程序需要加载一个类时,它会先委派给自己的父类加载器去加载,最终通过一层层传导至最顶层。
如果父类加载器没找到这个类,就会往下寻找子类加载器去加载。
关于 jvm 类加载机制,本文只是做了简单的入门总结。里面有很多点没有展开来讲,甚至一些难点也只是简单提了一下。后续会继续补充~~
冰冻三尺非一日之寒,要想把上面的知识牢记于心,不是一件易事, 需要花很多时间理解和总结。