类加载机制

类加载机制

学习思路参考

a)、类加载机制就是把我们编写好的class文件加载到内存模型中,了解这个后面试被问到“能否直接定义java.lang.String”或者“ClassNotFoundException出现在哪一步”就能很快回答上来并说明为什么
b)、类加载机制包括(载(装载) 连接【验证、准备、解析(可选)】 初始化 使用 卸载),即类的声明周期。加载过程使用的是加载器,核心就是双亲委派模型。

类加载机制

概述

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象(即实例对象),用来封装类在方法区内的数据结构,整个结构可以如下解:

【备注】对象属性的具体填充在后续步骤中,具体可以参照对象的创建过程

加载.class文件的方式

  • 从本地系统中直接加载
  • 通过网络下载 .class 文件(URLClassLoader)
  • 从 zip、jar 等归档文件中加载 .class 文件
  • 从专有数据库中提取 .class 文件
  • 将 Java源文件动态编译为 .class 文件

类加载机制详解

jiazai1

  • 类加载机制:加载(装载) 连接【验证、准备、解析(可选)】 初始化 使用 卸载
  • 装载阶段基本动作:
    • 通过类型的完全限定名,产生一个代表该类型的二进制数据流;
    • 解析这个二进制数据流为方法区内的内部数据结构;
    • 创建一个表示该类型的java.lang.Class类的实例
  • 连接阶段(了解):
    • 验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。
    • 准备,Java虚拟机为类变量分配内存,设置默认初始值。
    • 解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。
  • 初始化:当一个类被主动使用时,即创建对象、使用静态方法或者字段,反射、初始化子类等等,Java虚拟就会对其初始化 。
  • 参照范例了解一下类的加载过程:
class MyClass1 {  
    static {//静态块  
        System.out.println("static block ");  
    }  
}  
public class Step2Demo1 {  
    Class[] classArray = {  
            MyClass1.class//这样引用该类,必然需要将该类加载到虚拟机中  
    };  
    public static void main(String[] args){ 
        //MyClass1 my = new MyClass1();
        System.out.println("hello word");  
        //输入结果只有"hello word",没有"static block ""
    }  
}

类加载器

java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象.

两种类型的类加载器

  • Java虚拟机自带的类加载器
    • 根类加载器 (Bootstrap classloader)
      • 使用c++编写,我们无法在java代码里面获得该类 【从系统属性sun.boot.class.path所指定的目录加载类库,比如java.lang.*】
    • 扩展类加载器(Extension classloader)
      • java编写 【从java.ext.dirs系统属性所指定的目录中加载类库或者从jdk的安装目录的jre\lib\ext子目录下(扩展目录)下加载类库】
    • 应用程序类加载器(Application ClassLoader)
      • java编写 【从环境变量classpath或者系统属性java.class.path所指定的目录下加载类库,用户自定义的类加载器的父类加载器】。该类加载器也称为系统类加载器(System ClassLoader),开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
  • 用户自定义的类加载器
    • java.lang.ClassLoader的子类,用户可以定制类的加载方式

类加载器的层次关系如下,即双亲委派模型:
jiazai4

类加载的父委托机制

JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
类加载器均是继承自java.lang.ClassLoader抽象类。

参照如下的代码:

public class TestClassLoader {
    public static void main(String[] args) {
        ClassLoader loader = TestClassLoader.class.getClassLoader();
        System.out.println(loader.toString());
        System.out.println(loader.getParent().toString());
        System.out.println(loader.getParent().getParent());
    }
}

观察打印结果:

sun.misc.Launcher$AppClassLoader@500c05c2
sun.misc.Launcher$ExtClassLoader@454e2c9c
null

第一行打印的是应用程序类加载器(默认加载器),第二行打印的是其父类加载器,扩展类i加载器,按照我们的想法第三行应该打印启动类加载器的,这里却返回的null,原因是getParent(),返回时null的话,就默认使用启动类加载器作为父加载器.

思考

  • ClassNotFoundException解决方法
  • 题目:
    下面有关java类加载器,说法正确的是?
    a.引导类加载器(bootstrap class loader):用来加载Java核心库,是用原生代码来实现的
    b.扩展类加载器(extensions class loader):用来加载Java的扩展库
    c.系统类加载器 (system class loader):根据Java应用的类路径(CLASSPATH)来加载Java类
    d.tomcat为每个App创建一个Loader,里面保存着此WebApp的classLoader。需要加载WebApp下面的类时,就取出ClassLoader来使用
    正确答案为:A,B,C,D

类的初始化执行顺序

  • 静态变量、静态代码块初始化顺序级别一致,谁在前,就先初始化谁。从上而下初始化(只在类加载时,初始化一次)
  • 非静态变量、非静态代码块初始化顺序级别一致,谁在前,就先初始化谁。从上而下初始化(只要对象实例化一次,就初始化一次)
  • 构造方法在非静态变量、非静态代码块之后执行。
  • 子类静态变量、静态代码块在父类的静态变量、静态代码块之后执行。
  • 子类非静态变量、非静态代码块在父类构造方法之后执行。
  • 子类构造方法在父类构造方法之后执行。
  • 静态方法不会被子类重写。
posted @ 2022-06-22 09:49  Faetbwac  阅读(58)  评论(0编辑  收藏  举报