深入ClassLoader-JVM类加载器

JVM内置三大类加载器

JVM为我们提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到JVM的内存中,并且严格遵守着父委托的机制。
在这里插入图片描述


根类加载器

根加载器又称为Bootstarp类加载器,该类加载器是最为顶层的加载器,其没有父加载器,它是由C++编写的,主要负责虚拟机核心类库的加载,比如整个java.lang包都是由根加载器所加载的。根类加载器从系统属性sun.boot.class.path所指定的目录加载类库。

public static void main(String[] args) {
		System.out.println(System.getProperty("sun.boot.class.path"));
	}

打印结果:

C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\classes


扩展类加载器

扩展类加载器的父加载器是根加载器。它从java.ext.dirs系统属性所指定的目录下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader的子类

public static void main(String[] args) {
		System.out.println(System.getProperty("java.ext.dirs"));
	}

打印结果:

C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext


系统(System)加载器

系统加载器又称为应用加载器,它的父加载器是扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录下加载类。它是用户自定义的类加载器的默认父加载器,系统加载器是纯Java类,是java.lang.ClassLoader的子类。每一个类的构造中都会有对应ClassLoader的引用。

public static void main(String[] args) throws InterruptedException, ClassNotFoundException {	
		//反射构造某个类
		Class<?> klass = Class.forName("com.Reyco.MyThread.Singleton");
		System.out.println(klass.getClassLoader());
		System.out.println(klass.getClassLoader().getParent());
	}

打印结果:

sun.misc.Launcher$AppClassLoader@73d16e93

sun.misc.Launcher$ExtClassLoader@15db9742


父委托机制

父子加载器并非继承关系,子加载器不一定继承了父加载器,两者之间是种包装关系。
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层级的类加载都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
当我们自定义了java.lang.String试图引发原生java.lang.String的冲突时,很遗憾我们无法达成此目的。当我们尝试加载自定义的java.lang.String类时,系统加载器将请求委托交给父类加载器直到传递到根类加载器,而根类加载器的加载路径下有原生的java.lang.String,所以并不会加载我们自定义的String类。
父类加载器的优点:提高系统安全性,在此机制下,用户自定义的类加载器不可能加载父加载器本应加载的可靠类,可以防止恶意代码代替父类加载器的可靠代码

打开ClassLoader源码

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //本地方法,检查这个class是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    	//层层提交到父加载器
                        c = parent.loadClass(name, false);
                    } else {
                    	//如果父加载器为空,直接提交到根加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
				//如果此时仍然未加载成功,则执行findClass()方法
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //ClassLoader下实现的findClass只是抛出异常,未进行其他操作
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

自定义类加载器

所有的自定义类加载器都是ClassLoader的直接子类或间接子类,同时需要实现findClass方法,否则将会抛出Class找不到的异常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
public class DiyClassLoader extends ClassLoader {
	//默认加载目录
	private final static String DEFAYLT_DIR="D:\\ClassLoader";
	
	private String dir=DEFAYLT_DIR;
	
	private String classLoaderName;
	
	public DiyClassLoader() {
		super();
	}
	
	public DiyClassLoader(String classloadername) {
		super();
		this.classLoaderName=classloadername;
	}
	
	public DiyClassLoader (String classloadername,ClassLoader parent){
		super(parent);
		this.classLoaderName=classloadername;
	}
	
	public void setDir(String dir) {
		this.dir=dir;
	}
	
	public String getDir() {
		return this.dir;
	}
	
	public String getClassLoaderName() {
		return this.classLoaderName;
	}
	
	//重写findClass
	//按照父委托机制,先将委托请求层层提交到父加载器下,若是找不到则执行findClass()方法
	//自定义MyObject路径不在JVM内置的三大类加载器下
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		//将"."替换成"/"
		String classpath = name.replace(".","/");
		File classFile = new File(dir,classpath+".class");
		if(!classFile.exists()) {
			throw new ClassNotFoundException("文件找不到");
		}
		byte[] classBytes = loadClassBytes(classFile);
		if(null==classBytes || classBytes.length==0) {
			throw new ClassNotFoundException("加载失败");
		}
		//返回class
		return this.defineClass(name, classBytes,0,classBytes.length);
		
	}

	private byte[] loadClassBytes(File classfile) {
		try {
				//文件数组输出流
				ByteArrayOutputStream baos =new ByteArrayOutputStream();
				FileInputStream file = new FileInputStream(classfile);
				byte[] buffer = new byte[1024];
				int len=0;
				while((len=file.read(buffer))!=-1) {
					baos.write(buffer, 0, len);
				}
				baos.flush();
				return baos.toByteArray();
		}catch(Exception e) {
			return null;
		}
	}
}

测试类:
之前自定义类Myobject

public static void main(String[] args) throws InterruptedException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {	
		DiyClassLoader diyClassLoader = new DiyClassLoader("MyClassLoader");
		Class<?> klass=diyClassLoader.loadClass("com.Reyco.MyThread.MyObject");
		System.out.println(klass);
		System.out.println(klass.getClassLoader());
		//会调用MyObject的静态代码块“成功加载MyObjcey类”
		Object obj = klass.newInstance();
		//执行MyObject中的hello()方法
		Method method = klass.getMethod("hello",new Class<?>[] {});
		Object string=method.invoke(obj,new Object[] {});
		System.out.println(string);	
	}

打印结果:

class com.Reyco.MyThread.MyObject
com.Reyco.MyThread.DiyClassLoader@6d06d69c
成功加载MyObject类
Hello World


打破父委托机制

JDK提供的双亲委托机制并非强制性模型,我们可以对其进行覆写,实现父委托机制的打破。

@Override
	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
		Class<?> klass = null;
		//核心类包仍然交给内置类加载器加载
		if(name.startsWith("java.")) {
			try {
				ClassLoader system = ClassLoader.getSystemClassLoader();
				klass=system.loadClass(name);
				if(klass!=null) {
					if(resolve) {
						resolveClass(klass);
					}else {
						return klass;
					}
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		//自己的类直接进行findClass,避过父委托机制
		//实现父委托机制的打破
		try {
			klass=findClass(name);
		}catch(Exception e) {
			e.printStackTrace();
		}
		if(klass==null && getParent()!=null) {
			getParent().loadClass(name);
		}
		return klass;
	}

类加载器的命名空间与运行时包

类加载器的命名空间

  • 每个类加载器都有自己的命名空间,命名空间是由该加载器及其所有父加载器所构成的,因为在每个类加载器中同一个class都是独一无二的。

注意

  • 同一个ClassLoader下加载的类,实例对象的class对象都是同一个
  • 不同ClassLoader加载的类,实例对象对应的class对象不是同一个
    定义两个加载器去加载同个class:
public static void main(String[] args) throws InterruptedException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {	
		DiyClassLoader diyClassLoader1 = new DiyClassLoader("MyClassLoader1");
		DiyClassLoader2 diyClassLoader2 = new DiyClassLoader2("MyClassLoader2");
		Class<?> klass1=diyClassLoader1.loadClass("com.Reyco.MyThread.MyObject");
		Class<?> klass2=diyClassLoader2.loadClass("com.Reyco.MyThread.MyObject");
		System.out.println(klass1.hashCode());
		System.out.println(klass2.hashCode());
	}

打印结果:

865113938
1442407170

我们发现哈希码不同,这两个clas对象是不同的。
在这里插入图片描述在类加载器进行类加载时,首先会到记录表也就是缓存中,查看该类是否已经被加载,如已经加载过了,就不会重复加载,否则被认为是首次加载。

运行时包

我们在编写代码时通常会给一个类指定一个包名,包的作用是组织类,防止不同包下同样名称的class引起冲突,包名和类名构成了类的全限定名。在JVM运行时class有一个运行时包,运行时的包时由类加载器的命名空间和类的全限定名称构成的。

猜你喜欢

转载自blog.csdn.net/weixin_40288381/article/details/87559408