阅读 64

JVM类加载

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对象。

  1. 由谁加载?

    类加载器

  2. 怎么加载?

    双亲委派

  3. 什么双亲委派?

    BootstrapClassLoader(启动类加载器) -> ExtClassLoader(扩展类加载器) -> AppClassLoader(系统类加载器) -> CustomClassLoader(用户自定义类加载器)

    当一个类加载器去加载类时,会先看看父加载器有没有加载,加载过就直接返回,以此类推往上找,最后都没找到,那就只能自己来加载了。

  4. 为什么要用双亲委派?

    主要是出于安全考虑

  5. 如何自定义类加载器?

    模板方法模式,重写ClassLoader中的findClass()方法即可。

  6. 如何打破双亲委派?

    重写loadClass方法,不按ClassLoader的模板方法走。

4.2 Linking

Linking分为三步

  1. Verification
    1. 验证文件是否符合JVM规定,比如:是不是CAFE BABY开头啊。
    2. 静态成员变量赋默认值,比如:int类型默认是0,对象类型默认是null。
  2. Preparation 1. 静态成员变量赋默认值
  3. Resolution
    1. 将类、方法、属性等符号引用解析为直接引用 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用。

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 )

复制代码