Java类加载机制

一、为什么需要有类加载机制?

  通常我们的程序都是在编辑器比如eclipse上编写的,充其量也只是一些文本,这些内容和逻辑要怎么执行呢?有些人可能会说java是通过执行字节码文件来运行程序的,我们编译过后会生成字节码文件(.class文件),那请问:在执行字节码文件之前要做什么?其实就是把字节码文件加载到JVM的内存当中,只有在内存中才能运行!

二、先简单认识类要被加载到的地方——JVM内存

  当我们启动一个java程序的时候,就会启动一个JVM虚拟机进程,这个进程包含所有这个程序所产生的线程,同一个JVM的所有线程、所有数据都放在了同一个JVM进程所包含的内存空间中,即共享该JVM进程的内存区域(一家人就是要在一起嘛)。不同的JVM进程不会共享内存。

三、JVM进程什么时候会结束?

  当出现如下情况中的任何一种的时候,JVM进程结束:JVM进程结束的时候,所包含内存中的所有数据和状态都会丢失!

1、程序正常执行到结尾,结束;

2、程序中使用了System.exit(0)或者是Runtime.getRuntime().exit(0);中的一种语句,主动结束程序;

3、程序中发生了未捕获的异常或者是错误,导致程序结束;

4、所在平台强制结束JVM进程;

四、类加载的过程

  小前言:地球人都知道,Java通过new操作符来产生对象的时候,都是会产生实例对象,“类”信息就是模板,这个“模板”在被使用之前,都必须通过类加载器(类似于搬运工,后面会讲到)加载到JVM内存中,供程序后续的操作使用。

  当程序主动使用一个类的时候,JVM虚拟机会检查这个类是否被加载到了内存中,如果没有,则进行加载、连接、初始化三个步骤完成类加载!(当然JVM规范允许对类进行“预加载”,不需要等到第一次使用的时候才加载)

  1、加载过程之步骤一:类的加载

  类加载指的是把字节码.class文件加载到内存中,为每个字节码文件生成java.lang.Class对象(每个类其实也是一个实例,都是java.lang.Class的一个实例),程序使用任何类的时候,都会为之生成一个java.lang.Class对象,保存在JVM的内存中。

  类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的类加载器一般称为系统类加载器。关于加载器体系,后面会讲到;

  2、加载过程之步骤二:连接

  连接阶段会把类的二进制数据合并到JRE中,类连接总共分为三个阶段执行:  

  (1)验证:检查被加载的类是否有正确的内部结构(规范),是否和其他类协调一致;

  (2)准备:负责为类变量(static)分配内存,设置初始化值;

  (3)解析:将类中的二进制数据中的符号引用改为直接引用(欢迎各路大神对这点进行评论和进一步解释);

  3、加载过程之步骤三:类的初始化

  此阶段主要是对类进行初始化,主要初始化:类变量。 类变量的初始化方式有两种:(1)声明时直接赋初始值、(2)使用静态初始化块进行赋值;

       例如有 

static int value = 5; 

  或者有初始化块:

  static{

    value = 100;

  }

  JVM会顺序执行这些初始化的指令,static的值将会是代码中最后一次赋值时候的值;

  在类的初始化的步骤通常有这三个:

  (1)发现类没有被加载和连接,那么就会执行加载和连接;

  (2)类有直接的父类没有被初始化,那就先执行父类的初始化;

  (3)以此执行初始化语句,主要是针对类变量的初始化;(实例变量的初始化要在new对象的时候才会进行);

  因为类如果有父类,则必须进行父类的初始化1-3的步骤,以此类推,程序会把使用到的类的直接、间接父类全部执行上述3个步骤,保证所有父类都被初始化,因为java.lang.Object是所有类的祖宗类,所以Object总是会被JVM最先执行上述步骤!

  五、类加载器体系

  1、要把类加载到内存,必须要有加载的工具——类加载器!类加载器负责从磁盘中或者网络上加载字节码文件,并在JVM内存中生成java.lang.Class对象。

  2、类不会被重复加载,也不需要被重复加载,也就是说:一旦被加载后,可以无限次被使用,除非JVM进程结束!

  3、在java源码中类使用包名+类名进行标识,在JVM中,类被包名+类名+加载器作为标识,所以同样路径的类被不同的加载器加载也可以被区分。

  加载器体系:

  bootStrap加载器称为根类加载器,负责加载系统核心类;

  Extension加载器称为拓展类加载器,负责加载拓展的常用类;

  System加载器就是系统加载器,通常由它来完成我们编写的类;

  用户可以自定义加载器来加载字节码!

  一个类的加载过程由谁负责:

  首先由当前线程使用的类加载器尝试进行加载,该类加载器会先请求父类加载器进行检查,直到顶端(根类加载器),如果父类没有加载并且不能加载,则由当前加载器加载。(溯源委派模式)。

  一个测试类,获取当前类加载器,打印出父类加载器和父类的父类加载器:

        Thread t = Thread.currentThread();
        ClassLoader loader = t.getContextClassLoader();
        System.out.println(loader);
        System.out.println(loader.getParent());
        System.out.println(loader.getParent().getParent());

 

打印结果如下:

sun.misc.Launcher$AppClassLoader@5c647e05
sun.misc.Launcher$ExtClassLoader@3d4eac69
null

其中系统类加载器是AppClassLoader的一个实例;

拓展类加载器PlatFormClassLoader的一个实例;

最后的拓展类加载器的父类加载器是根类加载器,但打印出的是null,因为根类加载器没有继承ClassLoader,它是C语言实现的不是Java实现的,所以查看不到。

写在最后:

  第一次写技术类博文,希望各路大神多加指正!也欢迎补充!在技术的路上一起成长,共勉之。

posted @ 2019-10-07 19:28  Viking_牧马人  阅读(98)  评论(0)    收藏  举报