Java类加载机制

深入理解Java中IO流的使用技巧

Posted by yourzeromax on May 21, 2018

写在前面

hey!本来这篇博客是要在昨天写好发出的,但是昨天是520,忙着和女朋友过节,所以就推迟一天啦~之所以要写这篇博客,是因为我在之前面试的时候被问到这个问题,之后就下定决心来研究研究Java类的加载机制,类加载机制其实并不能真正帮助我们提升编程能力,但是如果想要在职业生涯中往架构师发展的话,去了解JVM的工作原理以及类的加载机制还是非常有必要的,当然,估计对于刚毕业的人来说,类加载机制更重要的是和我一样去应付面试官吧,哈哈哈~

6月18日补充,临近毕业也有了些时间去图书馆看书,在借阅《Java编程思想》和《深入理解Java虚拟机》两本书后,文中观点发生了部分变化,更容易理解。

什么是Java类加载?

Java的核心是Java虚拟机(JVM,也是跨平台的根本),我们编写的代码是.java文件,而虚拟机并不能识别代码的内容,因此的加载指的便是将类的.class(.java文件转换的字节码文件)文件中的二进制数据读入内存中,将其放在运行时数据区域的方法去内,然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。class对象是一一对应关系,使得我们能够通过反射的方式去寻找到我们需要的类。

类加载的生命周期

对于一个完整的类,它的生命周期分为7个阶段:

类加载生命周期

如果将验证、准备、解析看成是连接的话,Java类加载也被分为五个部分:
加载->连接->初始化->使用->卸载

  1. 加载:查找并且导入class文件,生成class对象到内存方法区中
  2. 验证:分别进行文件格式验证、元数据验证、字节码验证、符号引用验证,确保导入的信息符合虚拟机的要求,不会危害自身的运行安全。
  3. 准备:为类变量(静态变量)分配内存空间并设置变量初始值。基本数据类型分别初始为0或者0.0等,而引用类型初始化为null。
  4. 解析。:解析是把符号引用替换为直接引用的过程,这一步可不用太关心。
  5. 初始化:初始化的过程就是执行类构造器的过程(如:一些静态变量的赋值语句、静态代码块)。

后面两步本文选择忽略。

类加载器

类的加载需要通过一个叫类加载器的东西,而这个东西一般是用Java自带的三种:

类加载生命周期

  1. 根类加载器:使用c++编写(BootStrap),负责加载rt.jar
  2. 应用加载器:java实现(AppClassLoader),一般我们自己定义的类都是通过AppClassLoader进行加载
  3. 扩展类加载器:java实现(ExtClassLoader)

通过这三种类加载器,根据不同的类选择不同的加载器。

双亲委派机制

类的加载器的加载是使用的双亲委派的机制,什么是双亲委派呢?假设有A类的加载器是AppClassLoader,AppClassLoader不会自己去加载类,而会委ExtClassLoader进行加载,那么到了ExtClassLoader类加载器的时候,它也不会自己去加载,而是委托BootStrap类加载器进行加载,就这样一层一层往上委托,如果Bootstrap类加载器无法进行加载的话,再一层层往下走。

为什么要使用双亲委派加载机制呢?Java虚拟机在判断两个类是否相同的前提是这两个类都是在同一个加载器进行加载的,如果不使用双亲委派,那么一个类可能会被不同的类加载器加载,而这时在使用的时候,虚拟机中就会出现两个相同的类,进而造成程序的混乱,所以说,双亲委派机制非常重要,当然这个知识点只需要了解便好,因为我们也不可能去进行虚拟机内核的代码开发。

类的初始化

应该说,在这个生命周期的工作才是我们最应该关心的地方, 类加载的时机是在我们“首次主动使用”的时候,这个条件有六种情况会被触发:

  1. 创建对象的实例:我们new对象的时候,会引发类的初始化,前提是这个类没有被初始化。
  2. 调用类的静态属性或者为静态属性赋值
  3. 调用类的静态方法
  4. 通过class文件反射创建对象
  5. 初始化一个类的子类:使用子类的时候先初始化父类
  6. java虚拟机启动时被标记为启动类的类:就是我们的main方法所在的类

上述六种情况发生时,才会触发类的加载过程,同时类只会被加载一次,并且如果在编译时候就能确定某个静态变量的值时,便不会进行初始化操作,假如一个类有父类并且父类还没有初始化时,会先初始化父类。

类的初始化流程

一般来说如果类没有父类,它的流程为:

1)类的静态属性
2)类的静态代码块
3)类的非静态属性
4)类的非静态代码块
5)构造方法

如果有父类的话,流程有一些变化:

1)父类的静态属性
2)父类的静态代码块
3)子类的静态属性
4)子类的静态代码块
5)父类的非静态属性
6)父类的非静态代码块
7)父类构造方法
8)子类非静态属性
9)子类非静态代码块
10)子类构造方法

上面的初始化顺便是一定的,换言之,能够死记硬背,这往往用在类变量谁先被初始化以及哪段代码先被执行的问题之中,类的初始化内容还是比较容易混淆的,大家可以自己去写一些demo,思考一些类似的问题,再去验证一下。

思考问题

摘取网上一道经典的类加载问题恭大家思考,之后会附上一篇博客进行解析:

public class Singleton {
  private static Singleton singleton = new Singleton();
  public static int counter1;
  public static int counter2 = 0;
  private Singleton() {
      counter1++;
      counter2++;
  }
  public static Singleton getSingleton() {
      return singleton;
  }
 }

使用上述的Singleton类进行下述操作:

public class TestSingleton {
  public static void main(String args[]){
      Singleton singleton = Singleton.getSingleton();
      System.out.println("counter1="+singleton.counter1);
      System.out.println("counter2="+singleton.counter2);
  }
}

结果会是怎样的呢?

欢迎大家关注
我的博客