欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ClassLoader类加载器

程序员文章站 2022-07-03 14:28:17
...

一、ClassLoader 定义

ClassLoader的作用就是根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它转化成一个java.lang.Class类的一个实例。

所有Class都是由classloader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给java虚拟机进行连接、初始化等操作。

JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。

二、分类

BootStrap ClassLoader

BootStrap ClassLoader 是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar 和 java.lang 包下的文件等。由C++ 编写,不能被java程序直接调用,是虚拟机的一部分。

Extendsion ClassLaoder

加载扩展库 javax.* ; 主要加载\bin\ext 下的类库。
由java编写,可以把自己写的包放进去,也就是说开发者可以直接使用这个类加载器。

App ClassLoader

加载用户类路径(CLASSPATH)下的类库,用户自己的代码以及第三方jar包通常由APP ClassLoader 来加载。一般情况下这就是系统默认的类加载器。

自定义 ClassLoader

用户可自定义 ClassLoader 只要传入defineClass的二进制流是合法的,就可以通过不同形式去加载。如访问远程网络获取二进制流。
通过重写findClass方法可以对加密过的class进行解密、实现字节码增强技术、
ASM 等。

三、双亲委派机制

3.1、类加载器的层次

首先明确一个类加载器的层次结构,ClassLoader并没有继承其他类,但有一个 parent属性记录了当前类加载器的父类加载器
自定义 ClassLoader 的父类加载器是 App ClassLoader
App ClassLoader 的父类加载器是 Extendsion ClassLaoder
Extendsion ClassLaoder 没有父类加载器,它的 parent 是 null

public abstract class ClassLoader {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    // 委托的父类加载器
    // 注意:VM硬编码了这个字段的偏移量,因此所有的新字段都必须添加“after” 
    private final ClassLoader parent;

    // 类加载器的名字
    private final String name;

3.2、双亲委派机制

当需要加载某个类时,类加载器不会第一时间进行加载,而是查看父类是否加载过该类。如果父类已经加载过这个类,就直接使用该类不再加载,如果父类不曾加载过该类,就检查父类的父类是否加载过。
当检查到 BootStrap ClassLoader 都没有加载过该类的话,由 BootStrap ClassLoader尝试加载该类,如果加载失败就交给 BootStrap ClassLoader 的子类尝试加载。若任加载失败,就交给子类的子类进行加载以此类推。

ClassLoader类加载器

ClassLoader 中的 loadClass 方法

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    // 为了避免多个线程同时进行加载造成重复加载一个类,所以在这里加一个同步锁
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 检查当前类加载器的 parent 是否为空
                    if (parent != null) {
                        // parent 不为空让 parent调用此方法
                        c = parent.loadClass(name, false);
                    } else {
                        // parent 为空就让 BootStrap ClassLoader去尝试加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果没有找到类,则抛出ClassNotFoundException
                    // 异常来自非空父类加载器
                }

                if (c == null) {
                    // 如果仍未找到,则按顺序调用findClass,去查找类
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 这是定义类加载器,并记录数据
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

为了避免重复加载,当父亲已经加载了该类的时候,就没有必要 ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

四、自定义一个类加载器

提前在桌面建一个 Wali.java 文件,并编译出 Wali.class 文件待用。

public class Wali{
  static{
    System.out.println("Wali");
  }
}
package test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class myClassLoader extends ClassLoader {
	private String path;
	private String className;

	public myClassLoader(String path, String className) {
		this.path = path;
		this.className = className;
	}

	// 寻找类文件
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] b = loadClassData(name);
		return defineClass(name, b, 0, b.length);
	}

	// 加载类文件
	public byte[] loadClassData(String name) {
		name = path + name + ".class";
		InputStream in = null;
		ByteArrayOutputStream out = null;
		try {
			in = new FileInputStream(new File(name));
			out = new ByteArrayOutputStream();
			int i = 0;
			while((i = in.read()) != -1) {
				out.write(i);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				out.close();
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return out.toByteArray();
	}
}

package test;

public class ClassLoaderChecker {

	@SuppressWarnings("all")
	public static void main(String[] args)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		myClassLoader myClass = new myClassLoader("C:\\Users\\Administrator\\Desktop\\", "Wali.class");
		Class c = myClass.loadClass("Wali");
		System.out.println(c.getClassLoader());
		c.newInstance();
	}

}