Java基础:Java类加载器和加载机制

star2017 1年前 ⋅ 85 阅读

类加载器就是寻找类的字节码文件并构造出类在JVM内部表示对象的组件。

类加载工作由ClassLoader及其子类负责。ClassLoader是一个重要的Java运行时系统组件,它负责在运行时查找和装入Class字节码文件。

类加载器

负责将.class文件加载到内存中,并为之生成对应的 Class 对象。

类加载器层次结构

  1. Bootstrap ClassLoader 根类加载器
    • 也被称为引导类加载器,负责Java核心类的加载。此加载器是C写的,在Java是看不到的。
    • 加载Java JDK核心类 JRE/lib/rt.jar,如 System, String类。
  2. Extension ClassLoader 扩展类加载器
    • sun.misc.Launcher$ExtClassLoader
    • 负责加载JRE/lib/下的扩展目录ext中的jar类包。
  3. App ClassLoader 应用类加载器
    • 也称为系统类加载器,sun.misc.Launcher$AppClassLoader
    • 负责在 JVM 启动时加载来自 java 命令的 class 文件,以及 Classpath 环境变量所指定的 jar 包和类路径。

类加载器的流程

在Java中,类加载器把一个类装入JVM,需要经过加载、链接(校验,准备,解析)、初始化步骤。

加载

把类的.class文件的字节码加载到内存中,有两种方式:

  1. 隐式加载:不通过代码里调用ClassLoader来加载需要的类。而是通进JVM来自动加载需要的类到内存。
  2. 显式加载:调用ClassLoader类来加载一个类,可以使用以下方法:
    • this.getClass.getClassLoader.loadClass(String name)
    • Class.forName(String className)
    • 或自己实现ClassLoader的 findClass()方法等。

连接

把类的二进制合到到JRE中,分三个阶段

  1. 验证:检验加载的类内部结构是否正确,并和其他类协调一致。
  2. 准备:为类静态变量分配内存,并设置默认值(仅包含类静态变量,不包含实例变量)。
  3. 解析:虚拟机将常量池中的符号引用替换为直接引用。

初始化

类静态变量初正确的初始值,执行静态代码块,执行构造方法等。

  • 类变量赋初始值:
    1. 声明类变量时指定初始化值。
    2. 使用静态初始化块为类变量指定初始值。(静态初始化块都将被当成类的初始化语句)
  • JVM初始化步骤:
    1. 若类没加载和连接,则先加载并连接该类。
    2. 若类的直接父类没有被初始化,则先初始化其直接父类。
    3. 若有初始化语句,则依次执行这些初始化语句
  • 类初始化时机
    1. 创建类的实例。
    2. 调用某个类的静态方法。
    3. 访问某个类或接口的类变量,或为该类变量赋值。
    4. 使用反身来强制创建该类或接口对应的java.lang.Class对象。
    5. 初始化某个类的子类,该子类的直接父类和所有间接父类都被初始化。
    6. 直接使用java.exe来运行某个主类。
  • final修饰的类变量的初始化:
    1. 如果类变量(静态变量)使用final修饰,并且它的值在编译时就确定下来,该变量相当于常量, 不会被初始化。static final String name = "Admin"; 。
    2. 如果使用final修饰类变量的值在不能在编译时确定下来,必须等到运行时才可以确定该类变量的值,则会被初始化。static final String nowtime = System.currentTimeMillis() + ""; 。

使用ClassLoader类的loadClass()方法来加载某个类时,该方法只加载该类;使用ClassforName()静态方法会强制初始化该类。

类加载器机制

Java中,一个类用其全限定类名(包括包名和类名)作为唯一标识。
JVM中,一个类用其全限定类名和其类加载器作为唯一标识(类名、包名、加载器的实例)。

  1. 全盘负责制:该类所有依赖的和引用的其他类将由该类的加载器负责载入,除非显式使用另外的加载器载入。
  2. 父类委托:先委托父类加载器加载目标类,找不到时再从自己的类路径中查找并加载目标类,确保类只被加载过一次。
  3. 缓存机制:保证所有加载过的类都会被缓存,当需要使用时,先从缓存区中搜索,缓存不存在该类时,系统才会读取该类对应的二进制数据,将期转换成Class对象,放入缓存区中。

类加载器执行步骤

类加载器加载Class大致经过如下8个步骤:

  1. 检测此类是否载入过(即在缓存区是否存),有进入第8步,没有执行第2步。
  2. 如果父类加载器存在,执行第3步;否则跳到第4步执行。
  3. 请求父类加载器载入目标类,如果成功跳到第8步,否则执行第5步。
  4. 请求根类加载器载入目标类,如果成功跳到第8步,否则执行第7步。
  5. 当前类加载器尝试寻找Class文件,如果找到则执行第6步,否则跳到第7步。
  6. 从文件中载入Class,成功载入后跳到第8步。
  7. 抛出ClassNotFoundException异常。
  8. 返回对应的java.lang.class对象

自定义的类加载器

  1. 通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。
    • loadClass(String name,boolean resolve):ClassLoader方法入口,根据指定名称为加载类。
    • findClass(String name)根据指定定名称来查找类。建议使用
    • Class defineClass(String name,byte[] b, int off, int len)
    • defineClass()
    • findSystemClass(String name)
    • static getSystemClassLoader()
    • getParent()
    • findLoadedClass(String name)
  2. 使用自定义类加载器实现如下常见功能:

    • 执行代码前自动验证数字签名。
    • 根据用户提供的密码解密代码,从而可以实现代码混淆来避免反编译*.class文件。
    • 根据用户需求来动态在加载类。
    • 根据应用需求把其他数据以字节码的形式加载到应用中。
  3. URLClassLoader类
    该类也是系统类加载器和扩展类加载器的父类。功能比较强大,可以从本地文件或远程主机获取二进制文件来加载类。

    • URLClassLoader(URL[] URLS):使用默认的父类加载器创建一个ClassLoader,该对象将从urls所指定的系列路径来查询并加载类。
    • URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,其他功能与个构造器相同。

一旦得到URLClassLoader对象之后,就可以调用该对象的loadClass()方法来加载指定类。例如:可以直接从文件系统中加载MySQL驱动,并使用该驱动来获取数据库连接,就无须将驱动添加到CLASSPATH环境变量中。

示例代码

示例一:获取加载器

public class Demo2 {

    @Test
    public void ClassLoaderTest() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        System.out.println("loader = " + loader);
        System.out.println("loader parent = " + loader.getParent());
        System.out.println("loader parent`s parent = " + loader.getParent().getParent());
    }
}

//----------结果------------------------
loader = sun.misc.Launcher$AppClassLoader@18b4aac2
loader parent = sun.misc.Launcher$ExtClassLoader@2038ae61
loader parent`s parent = null

示例二:URLClassLoader

package com.demo;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties;
public class Test6 {
     private static Connection conn;

     public static void main(String[] args) throws Exception {
           String url = "jdbc:mysql://localhost:3306/vending?useUnicode=true&characterEncoding=utf-8&useSSL=false";
           String account = "root";
           String key = "123456";
           System.out.println(getConn(url,account,key));
     }
     // 定义一个获取数据库连接的方法
     public static Connection getConn(String url, String user, String pass) throws Exception {
           if (conn == null) {
                // 创建一个URL数组
                URL[] urls = { new URL("file:mysql-connector-java-5.1.44-bin.jar") };
                // 以默认的ClassLoader作为父ClassLoader,创建URLClassLoader
                @SuppressWarnings("resource")
                URLClassLoader myClassLoader = new URLClassLoader(urls);
                // 加载MySQL的JDBC驱动,并创建默认实例
                Driver driver = (Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
                // 创建一个设置JDBC连接属性的Properties对象
                Properties props = new Properties();
                // 至少需要为该对象传入user和password两个属性
                props.setProperty("user", user);
                props.setProperty("password", pass);
                // 调用Driver对象的connect方法来取得数据库连接
                conn = driver.connect(url, props);
           }
           return conn;
     }
}

示例三:类加载顺序与赋值

public class MainTest {

    public static void main(String[] args) {
        Singleton s = Singleton.getSingleton();
        System.out.println("counter1 = " + s.counter1);// = 1
        System.out.println("counter2 = " + s.counter2);// = 0

    }
}

public class Singleton {

    /**
     * 1. 被调用:加载,连接 
     * 1.1 验证
     * 1.2 准备:给静态变量分配内存,设默认值,singleton=null,counter1=0,counter2=0
     * 1.3 解析:符号引用替换成直接引用
     * 2. 初始化:
     * 2.1 执行构造方法:counter1=1,counter2=1,singleton=对象(特别注意)
     * 2.2 给类变量赋正确的初始值:counter1没有赋初始值,还是默认值;counter2=0 (特别注意)
     */

    private static Singleton singleton = new Singleton();

    public static int counter1;
    //然后初始化,赋值覆盖了默认值
    public static int counter2 = 0;

    public Singleton() {
        counter1++; //counter1 = 1
        counter2++; //counter2 = 1
    }

    public static Singleton getSingleton() {
        return singleton;
    }
}

相关参考

深入理解Java类加载器(ClassLoader)

更多内容请访问:IT源点

相关文章推荐
  • 该目录下还没有内容!

全部评论: 0

    我有话说: