1.JVM是什么?
JVM是一种语言规范,只要符合规范,就可以在JVM上运行(跨语言的平台)
2.JVM能做什么?
看一下我们的java文件是如何编译运行的。
- 将java文件编译成class文件
- 将class文件载入内存由JVM解释执行
简单的说: jvm就是解释执行class文件的;
tips: 为什么说java是跨平台的语言,一次编译,到处运行?因为java文件最终是要编译成class文件,而只要符合jvm规范的class文件,就可以在jvm上解释执行。
就好比小时候玩的小霸王游戏一样,class文件相当于游戏卡,而jvm相当于小霸王主机,只要你有小霸王游戏卡,就可以在任何小霸王上玩,小明家的小霸王,小白家的小霸王都能玩。当然,前提是人家家里得有小霸王。
所以,java之所以能一次编译,到处运行,关键还是因为jvm啊,linux有可以在linux上跑的jvm,windows有可以在windows上跑的jvm,你只需要把class文件交给jvm处理就行了,所以java语言可以跨平台。
3.class文件内容
JClassLib IDE插件查看class文件:
4.类加载过程
类加载过程其实主要包含3大步,Loading -> Linking -> Initializing。 我们详细分析下:
4.1 Loading
Loading就是将class文件加载进JVM内存,解析成一个class对象。
-
由谁加载?
类加载器
-
怎么加载?
双亲委派
-
什么双亲委派?
BootstrapClassLoader(启动类加载器) -> ExtClassLoader(扩展类加载器) -> AppClassLoader(系统类加载器) -> CustomClassLoader(用户自定义类加载器)
当一个类加载器去加载类时,会先看看父加载器有没有加载,加载过就直接返回,以此类推往上找,最后都没找到,那就只能自己来加载了。
-
为什么要用双亲委派?
主要是出于安全考虑
-
如何自定义类加载器?
模板方法模式,重写ClassLoader中的findClass()方法即可。
-
如何打破双亲委派?
重写loadClass方法,不按ClassLoader的模板方法走。
4.2 Linking
Linking分为三步
- Verification
- 验证文件是否符合JVM规定,比如:是不是CAFE BABY开头啊。
- 静态成员变量赋默认值,比如:int类型默认是0,对象类型默认是null。
- Preparation 1. 静态成员变量赋默认值
- Resolution
- 将类、方法、属性等符号引用解析为直接引用 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用。
4.3 Initializing
调用类初始化代码 ,给静态成员变量赋初始值
5. 案例分析
class Test{
public static int count = 2;//1
public static Test t = new Test();//2
public Test(){
count ++;
}
public static void main(String[] args) {
System.out.println(Test.count);
}
}
//输出结果:3
分析加载过程:
1. 加载
2. 校验类文件
3. 静态成员变量赋默认值(count = 0,t = null)
4. 静态成员变量赋初始值(执行代码1处:count = 2 , 执行代码2处:t = new Test() -> 调用构造方法 count ++ ,所以最终结果为3 )
下面讲代码2 处和代码1 处交换位置,输出结果就不一样了。
class Test{
public static Test t = new Test();//2
public static int count = 2;//1
public Test(){
count ++;
}
public static void main(String[] args) {
System.out.println(Test.count);
}
}
//输出结果:2
分析加载过程:
1. 加载
2. 校验类文件
3. 静态成员变量赋默认值(count = 0,t = null)
4. 静态成员变量赋初始值(执行代码2处:t = new Test() -> 调用构造方法 count ++ ,此时count为 0++ = 1,
执行代码1处:count = 2 , 所以最终结果为2 )