ClassLoader类加载器

一、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 中的 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();
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值