虚拟机类加载机制代码部分

277 阅读6分钟

配合使用虚拟机类加载机制

一、主动使用的方式

1. 基本的主动使用方式

package club.codermax.classLoading;

/**
 * 1.
 *  JVM 参数:-XX:+TraceClassLoading
 * [Loaded club.codermax.classLoading.Test01 from file:/D:/%e9%9d%a2%e8%af%95/jvm/project/out/production/project/] 主动加载
 * [Loaded club.codermax.classLoading.SuperClass from file:/D:/%e9%9d%a2%e8%af%95/jvm/project/out/production/project/]
 * [Loaded club.codermax.classLoading.SubClass from file:/D:/%e9%9d%a2%e8%af%95/jvm/project/out/production/project/]
 *
 */

import java.util.UUID;

/**
 * 2.
 *  Hello World !
 * 常量在编译阶段会存入到 调用类(使用类) 的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发常量定义类的初始化
 * 编译完成后常量STR 与 ConstClass没有关系,将.class 文件删除之后也能运行输出
 * D:\面试\jvm\project\out\production\project>javap -c  club.codermax.classLoading.Test01 反编译
 */

/**
 * MyTest init
 * 825888fb-1fee-432f-a986-f5e3ba2fc23d
 * 3.
 * 当一个常量的并非编译期间可以确定的,那么它的值就不会放到调用类的常量池当中
 * 这时在程序运行时,会导致主动使用这个常量所在的类,进而会导致这个类的初始化
 */
public class Test01 {
    public static void main(String[] args) {
        // 1.
        // System.out.println(SubClass.value);

        // 2.
        // System.out.println(ConstClass.VAL3);

        // 3.
        // System.out.println(MyTest.STR);

        // 4.
        // SubClass sc1 = new SubClass();
        SubClass[] sc = new SubClass[5];  // 不会触发类的初始化
        System.out.println(sc.getClass()); // Lclub.codermax.classLoading.SubClass
        System.out.println(sc.getClass().getSuperclass()); // java.lang.Object

        int[] ints = new int[5];
    }
}

class SuperClass {
    static {
        System.out.println("SuperClass init");
    }
    public static int value = 123;
}

class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init");
    }
    //public static int value = 321;
}
class ConstClass {
    static {
        System.out.println("ConstClass init");
    }
    public static final String STR = "Hello World !";
    public static final int VAL1 = 6;
    public static final int VAL2 = 666;
    public static final int VAL3 = 5;
}

class MyTest{
    public static final String STR = UUID.randomUUID().toString();

    static {
        System.out.println("MyTest init");
    }
}

2. 对接口进行初始化

package club.codermax.classLoading;

import java.util.Random;

/**
 * 在对一个接口进行初始化的时候,不需要对其父接口进行初始化, 即一个接口不会因为它的子接口或实现类的初始化而初始化
 * 只有真正使用父接口的时候(如引用接口所定义的常量的时候)才会初始化
 * 并且接口中定义的常量默认便是public static final
 *
 * 另外,在初始化一个类时,并不会先初始化它所实现的接口
 */
public class Test02 {
    public static void main(String[] args) {
        System.out.println(Sub.c);
    }
}

interface Super{
    int a = 6;
    Thread thread = new Thread() {
        // 实例化代码块,每次初始化都会输出
        {
            System.out.println("hello Super");
        }
    };
}
interface Sub extends Super {
    int b = 8;
    int c = new Random().nextInt(2);
}

3. 验证反射对类的初始化

package club.codermax.classLoading;

/**
 * String是由启动类加载器加载的返回null
 * <p>
 * sun.misc.Launcher$AppClassLoader@18b4aac2 应用类加载器:主要加载classpath工程中的文件
 *
 *ClassLoader的loadClass方法加载一个类不会导致对一个类的初始化
 *
 */
public class Test04 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cla = Class.forName("java.lang.String");
        System.out.println(cla.getClassLoader());
        System.out.println("======================");


        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> cl = loader.loadClass("club.codermax.classLoading.C");
        System.out.println(cl);
        System.out.println("======================");
        Class<?> clas = Class.forName("club.codermax.classLoading.C");  // 反射会导致类的初始化
        System.out.println(clas.getClassLoader());
    }
}

class C {
    static {
        System.out.println("C init");
    }
}

二、双亲委托模型

1. 双亲委托模型初体验

package club.codermax.classLoading;

/**
 * null
 * sun.misc.Launcher$AppClassLoader@18b4aac2
 */
public class Test05 {
    public static void main(String[] args) {
        String[] str = new String[2];
        System.out.println(str.getClass().getClassLoader());

        Test05[] test05s = new Test05[2];
        System.out.println(test05s.getClass().getClassLoader());

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());  // null ,这个null和第一个null 不同,数组是原生类型没有类加载器
    }
}

2. 自定义类加载器

package club.codermax.classLoading;

import java.io.*;

/**
 * 自定义类加载器
 *
 * club.codermax.classLoading.Test01@1b6d3586
 *
 * 关于类加载器:每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成
 * 子加载器加载器的类能够访问父加载器所加载的类
 * 而父加载器所加载的类无法访问子加载器所加载的类
 */
public class Test06 extends ClassLoader{
    private String classLoadName;
    private String fileExtension = ".class";
    private String path;

    public void setPath(String path) {
        this.path = path;
    }

    public Test06(String classLoadName) {
        super(); // 默认将应用类加载器作为该类加载器的父加载器
        this.classLoadName = classLoadName;
    }

    public Test06(ClassLoader parent, String classLoadName) {
        super(parent);// 可以指定这个加载器的父加载器
        this.classLoadName = classLoadName;
    }



    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass invoked: " + className);
        System.out.println("classLoadName: " + this.classLoadName);

        byte[] data = this.loadClassData(className);

        /*for (byte a : data) {
            System.out.print(a + " ");
        }*/

        return this.defineClass(className, data, 0, data.length);  // 得到class对象
    }

    /**
     * 根据 className找到对应的文件,以输入输出流的形式,返回字节数组
     */
    private byte[] loadClassData(String className) {
        InputStream in = null;
        byte[] data = null;
        ByteArrayOutputStream out = null;
        className = className.replace(".", "\\");

        try {
            in = new FileInputStream(new File(this.path + className + this.fileExtension));
            out = new ByteArrayOutputStream();

            int oh = 0;
            while ((oh = in.read()) != -1) {
                out.write(oh);
            }
            data = out.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    /**
     * 因为双亲委派模型,在classpath下如果存在相应的class文件,loadClass会优先让父加载器加载(就算指定的路径也不行),
     * 只有将classpath下的class文件删除之后,系统类加载器不能加载,此时自定义加载器才会在指定路径中加载
     *
     * load2的父加载器是load1,一种加载器只会加载一次类,所以load2和load1 打印的classHashCode相同
     *
     * 命名空间
     * load3与load1是不同的加载器,所以Test01这个类会加载两次
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception{
        Test06 load1 = new Test06("load1");

        //load1.setPath("D:\\面试\\jvm\\project\\out\\production\\project");
        load1.setPath("D:\\面试\\jvm\\");

        Class<?> cla = load1.loadClass("club.codermax.classLoading.Test01");
        System.out.println("classHashCode: " + cla.hashCode());
        Object object = cla.newInstance();  // 通过反射的方式获得class对应的实例

        System.out.println(object);
        System.out.println();

        Test06 load2 = new Test06(load1,"load2");
        load2.setPath("D:\\面试\\jvm\\");

        Class<?> cla2 = load2.loadClass("club.codermax.classLoading.Test01");
        System.out.println("classHashCode: " + cla2.hashCode());
        Object object2 = cla.newInstance();

        System.out.println(object2);
        System.out.println();

        Test06 load3 = new Test06("load3");
        load3.setPath("D:\\面试\\jvm\\");

        Class<?> cla3 = load3.loadClass("club.codermax.classLoading.Test01");
        System.out.println("classHashCode: " + cla3.hashCode());
        Object object3 = cla.newInstance();

        System.out.println(object3);
        System.out.println();

        System.gc();
    }
}

三、线程上下文类加载器

1. 有关上下文类加载器的基本知识

package club.codermax.classLoading;

/*
    当前类加载器(Current Class Loader)
    每一个类都会使用自己的类加载器(即加载自身的哪个类加载器)来去加载其他类(指的是这个类所依赖的其他的类)
    eg: 如何ClassX引用ClassY,那么ClassX的类加载器就会取加载ClassY(前提是ClassY尚未被加载)


    线程上下文类加载器:ContextClassLoader,Thread类中getContextClassLoader和setContextClassLoader(ClassLoader cl)
    分别用来获得和设置上下文加载器

    如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文加载器
    Java应用运行的初始线程类加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类和资源

    线程上下文类加载器的作用
    改变父classloader不能使用子classloader或者其他没有直接父子关系的classloader加载的类的情况,即改变了双亲委托模型
    广泛使用在SPI 例如JDBC中

    线程上下文类加载器就是当前线程的当前类加载器current class loader

    在双亲委托模型下,类加载时由下至上的,即下层的类加载器会委托上层进行加载,但对于SPI来说,有些接口是有java核心库所提供的
    而Java核心库是由启动类加载器加载的,而这些接口的实现却来自不同的jar包(JDBC,由数据库厂商提供),
    Java的启动类加载器不会加载其他来源的jar包,这样传统的在双亲委托模型下就不能满足要求,而通过当前的线程设置上下文类加载器
    就可以由设置的上下文类加载器来完成接口实现类的加载
 */

public class Test08 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getContextClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2

        System.out.println(Thread.class.getClassLoader());
    }
}

2. 打破双亲委托模型的具体实现

package club.codermax.classLoading;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;

/*
    线程上下文类加载器的一般使用模式(广泛用于框架之中: 获取 - 使用 - 还原)
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    try {
        Thread.currentThread().setContextClassLoader(targetLoader);  // 获取
        myMethod();// 使用
    } finally {
        Thread.currentThread().setContextClassLoader(classLoader);// 最后一定要还原
    }


    如果高层提供了统一的接口让低层去实现,同时又要在高层加载或实例化低层的类时,就必须通过线程上下文来帮助高层的ClassLoader找到并加载该类
 */

// ServiceLoader驱动的规范

public class Test10 {
    public static void main(String[] args) throws Exception{

        //Thread.currentThread().setContextClassLoader(Test10.class.getClassLoader().getParent());

        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();

        while (iterator.hasNext()) {
            Driver driver = iterator.next();
            System.out.println("driver: " + driver.getClass() + ", loader: " + driver.getClass().getClassLoader());
        }

        System.out.println("当前线程上下文类夹杂器: " + Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader的类加载器: " + ServiceLoader.class.getClassLoader());


        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myJvmDB", "username", "password");
    }
}