Java-Class Loader

34 篇文章 13 订阅
7 篇文章 0 订阅

1. 类加载的三个阶段

  1. 三个阶段:
    1. 加载: 查找并加载类的二进制数据。
    2. 链接:
      1. 验证:确保被加载类的正确性。
      2. 准备:为类的静态变量分配内存,并将静态变量初始化为默认值。
      3. 解析:将类中符号引用变为直接引用。
    3. 初始化:为类的静态变量,赋予正确的初始值。

2. java 类主动使用

  1. 在java规范中, 只有类被首次主动使用的时候,才会被加载。
  2. 主动使用的情形:
    1. new 直接使用
    2. 访问某个类或是接口的静态变量,或者对该静态变量进行赋值操作。
    3. 调用了静态方法。
    4. 反射某个类。
    5. 初始化一个子类。
    6. 启动类;eg:java HelloWorld
  3. 特殊的情形:
    1. 使用子类去调用父类的静态变量,只会初始化父类,而不会初始化子类。
    2. new Obj[]: 定义数组也不会初始化类。
    3. 访问被final修饰的属性也不会被初始化。
      1. public static final long salary = 1000L; ====> 不会被初始化。 因为编译时就能算出结果。
      2. public static final int random = new Random().nextInt(100); ===> 会被初始化。 需要到运行时才能得到random产生的值。
  4. 除此之外,其余的都是被动调用,不会导致类的初始化
  5. 栈中存放的是对象的引用,类的数据结构存放在本地方法区中,堆中存放类的数据以及对类数据结构的指向句柄。

3. 类初始化

  1. 静态语句块只能访问以及修改之前的静态变量;之后定义的静态变量,只能修改,不能访问。
public class Demo1 {

    public static int x = 10;

    static {        x = 20;
        System.out.println("x: " + x);

        y = 30;
//        System.out.println("y: " + y); Error
    }
    public static int y = 20;
}

class Test {
    public static void main(String[] args) {
        System.out.println(Demo1.x); // 20
        System.out.println(Demo1.y); // 20 因为顺序初始化时, static中赋值的30, 被后面的赋值给覆盖.
    }
}
  1. 静态代码块,JVM已经保证了线程的安全性。

4. 类加载器以及自定义ClassLoader

在这里插入图片描述

  1. 自定义ClassLoader
public class MyClassLoader extends ClassLoader {

    private final static String DEFAULT_DIR = "E:\\classLoader\\"; // 存放需要加载class的路径

    private String dir = DEFAULT_DIR;

    private String classLoaderName;

    public MyClassLoader() {        super();
    }
    
    public MyClassLoader(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }
    
    public MyClassLoader(ClassLoader parent, String classLoaderName) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }
    
    /**
     * name传入的是classPath, 格式为: xxx.xxx.xxx.AAA.class
     * 所以, 为了能够读取到类文件中的数据, 需要转换为xxx/xxx/xxx/AAA.class
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {        
    	String classPath = name.replace(".", "/");
        File classFile = new File(dir, classPath + ".class");
        System.out.println("ClassFileName: " + classFile.getAbsolutePath());
        if (!classFile.exists()) {            
        	throw new ClassNotFoundException("The class " + name + " not found in " + dir);
        }
        
        byte[] classBytes = loadClassBytes(classFile);
        if (classBytes == null || classBytes.length == 0) {           
        	 throw new ClassNotFoundException("Load class file " + name + " failed");
        }
        
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }
    
    private byte[] loadClassBytes(File classFile) {
        try (ByteArrayOutputStream bais = new ByteArrayOutputStream();
             FileInputStream fis = new FileInputStream(classFile)) {
            // 设置的缓存区大小, 需要小于10, 否则会报错java.lang.ClassFormatError, 网上帖子说是因为class文件和JVM的
            // class验证冲突了. 可能class文件格式存在问题.
			// byte[] buffer = new byte[2014];
            byte[] buffer = new byte[1];
            while (fis.read(buffer) != -1) {                
            	bais.write(buffer);
            }
            bais.flush();
            return bais.toByteArray();
        } catch (IOException e) {            
        		e.printStackTrace();
            return null;
        }    
    }
}


public class ClassLoaderObject {

    static {
        System.out.println("Init ClassLoaderObject!");
    }
    public String hello() {
        System.out.println("Print Hello world!");
        return "Hello World!";
    }
 }


public class ClassLoaderTest {

    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader("MyClassLoader");

        // 加载class, 不能出现在当前的classPath下, 否则会使用ApplicationClassLoader, 而不会使用我们自己定义的
        // classLoader. 因为, JVM使用的是双亲委托机制; 当加载一个类, 会先让父classLoader加载此class; 若在父的classPath
        // 下找不到class, 才不断往子classLoader中传递; 最后若未找到则直接抛出ClassNotFoundException.
        Class<?> loadClass = classLoader.loadClass("com.alex.classLoader.ClassLoaderObject");
        System.out.println(loadClass);
        System.out.println(loadClass.getClassLoader());
    }
}

5. 打破父委托机制

  1. 自定义ClassLoader,并且重写loadClass方法。先使用我们自定义的classLoader加载class;若自定义的classLoader无法加载class,则使用系统classLoader,也就是使用父委托机制。但是对于
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {    
	Class<?> clazz = null;
    if (name.startsWith("java.")) { // java.开头的使用系统classLoader, 加载jdk的class.
        try {            
        	ClassLoader system = ClassLoader.getSystemClassLoader();
            clazz = system.loadClass(name);
            if (clazz != null) {                
            	if (resolve) {
                    resolveClass(clazz);
                }
                
                return clazz;
            }        
         } catch (Exception e) {
            e.printStackTrace();
        }    
  }

    try {
        clazz = findClass(name); // 使用自定义classLoader
    } catch (Exception e) {        
    	e.printStackTrace();
    }
    
    if (clazz == null && getParent() != null) { // 自定义的classLoader中没有此class, 使用父classLoader去加载.
        getParent().loadClass(name);
    }
    
    return clazz;
}
  1. 使用自定义的java.lang.String类。 结论:打破父委托机制,但是无法加载java.lang.*类,因为java对于java.lang包的类有安全校验, 不允许开发者定义一样的类。 eg:自定义一个java.lang.String是无法加载到classLoader中的;只能是使用jdk自带的。
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {    
	Class<?> clazz = null;
    /* 忽略这段代码, 不然还是会使用jdk的String类. 这样我们就可以使用自定义的classLoader去加载java.lang.String类。
    if (name.startsWith("java.")) { // java.开头的使用系统classLoader, 加载jdk的class.
        try {            
        	ClassLoader system = ClassLoader.getSystemClassLoader();
            clazz = system.loadClass(name);
            if (clazz != null) {                
            	if (resolve) {
                    resolveClass(clazz);
                }
                
                return clazz;
            }        
         } catch (Exception e) {
            e.printStackTrace();
        }    
  	}*/

    try {
        clazz = findClass(name); // 使用自定义classLoader
    } catch (Exception e) {        
    	e.printStackTrace();
    }
    
    if (clazz == null && getParent() != null) { // 自定义的classLoader中没有此class, 使用父classLoader去加载.
        getParent().loadClass(name);
    }
    
    return clazz;
}

6. 类的卸载和classLoader卸载

  1. JVM中只有class满足以下三个条件才能被GC.
    1. 该类所有的实例都已经被GC。
    2. 加载该类的classLoader已经被GC。
    3. 该类的java.lang.Class在任何地方都没有被引用。

7. 线程上下文

  1. 这里使用Mysql作为案列,进行分析;Thread.currentThread().getContextClassLoader();可以得到当前线程中所处的ClassLoader;也可以设置当前线程的上下文,以及其中的classLoader
Thread.currentThread().getContextClassLoader(); // 线程上下文中获取classLoader.
Class.forName("com.jdbc.mysql"); // 反射获取mysql.
/**
 * private static Connection getConnection(
 *         String url, java.util.Properties info, Class<?> caller) throws SQLException {
 *
 *          * When callerCl is null, we should check the application's
 *          * (which is invoking this class indirectly)
 *          * classloader, so that the JDBC driver class outside rt.jar
 *          * can be loaded from here.
 *
 *  ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
 *  synchronized (DriverManager.class) {
 *             // synchronize loading of the correct classloader.
 *  if (callerCL == null) {
 *
 *      // 从线程上下文中获取ApplicationClassLoader; 若是使用默认的classLoader为BootClassLoader;
 *      // 这样会导致无法注入mysql实例. 使用ApplicationClassLoader就可以从classPath中获取mysql的实现类;
 *      // 从而产生mysql实例.
 *      callerCL = Thread.currentThread().getContextClassLoader();
 *  }
 *}
 *
 *  if (url == null) {
 *      throw new SQLException("The url cannot be null", "08001");
 *  }
 */
Connection contection = DriverManager.getConnection("");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值