详述Java类加载机制

类加载器是什么?

Java类加载器(Java Classloader)负责动态地将Java类加载到Java虚拟机的内存空间内, 是Java运行时环境(Java Runtime Environment)的一部分,JVM默认有3个类加载器,每个类加载器负责加载特定位置的Java类。

一、Bootstrap ClassLoader(引导类加载器)

该类加载器通常由C++语言实现,不继承任何Java类,负责加载System.getProperty(“sun.boot.class.path”)所指定核心Java库,也可以通过java -Xbootclasspath指定其搜索路径。

public class Test {

	public static void main(String[] args) {
		String path = System.getProperty("sun.boot.class.path");
		String[] paths = path.split(";");
		for (String string : paths) {
			System.out.println(string);
		}		
	}
}

执行结果:
在这里插入图片描述
可见引导类加载器所指定的java库是在D:\Program_Files\Java\jre1.8.0_131\lib目录下,并且可以在D:\Program_Files\Java\jre1.8.0_131目录下创建classe文件夹,在里面存放需要被引导类加载器所加载的java文件。
注意:有的电脑在安装jdk时没有再次安装jre,而jdk中自带jre,所以没有再次安装jre的电脑输出路径为D:\Program_Files\Java\jdk1.8.0_131\lib目录

二、ExtClassLoader(扩展类加载器)

该类加载器由sun.misc.Launcher$ExtClassLoader类实现,
负责加载System.getProperty(“java.ext.dirs”)所指定的Java的扩展库,也可以通过java -Djava.ext.dirs指定其搜索路径,例如:java -Djava.ext.dirs=d:\classes HelloWorld;注意:如果将自己开发的 jar 文件放在System.getProperty(“java.ext.dirs”)所指定的目录中,也会被 ExtClassLoader类加载器加载。

public class Test {

	public static void main(String[] args) {
		String path = System.getProperty("java.ext.dirs");
		String [] paths = path.split(";");
		for (String string : paths) {
			System.out.println(string);
		}	
	}
}

执行结果:
在这里插入图片描述
可见引导类加载器所指定的java库是在D:\Program_Files\Java\jre1.8.0_131\lib\ext目录下。

三、AppClassLoader(系统类加载器)

该类加载器由sun.misc.Launcher$AppClassLoader类实现,负责加载System.getProperty(“java.class.path”)或CLASSPATH环境变量所指定的Java类,也可以加上-cp来覆盖原有的classpath设置,例如: java -cp ./classes HelloWorld;说明:默认情况下自定义类都由该类加载器加载。

public class Test {

	public static void main(String[] args) {
		String path = System.getProperty("java.class.path");
		String [] paths = path.split(";");
		for (String string : paths) {
			System.out.println(string);
		}	
	}
}

执行结果:
在这里插入图片描述
上面的输出结果与扩展类加载器和引导类加载器的输出结果有重叠,是为了保障如果两个父级类加载器出现问题确保类能够成功加载。

各级加载器之间的关系

在这里插入图片描述
在此验证一下:

public class Test {

	public static void main(String[] args) {
		ClassLoader classLoader= Test.class.getClassLoader();	
		System.out.println(classLoader.getClass().getName());
			
		classLoader = classLoader.getParent();	
		System.out.println(classLoader.getClass().getName());
		
		classLoader = classLoader.getParent();
		System.out.println(classLoader);
		//此时已经到达顶层的引导类加载器,此加载器是用c语言书写,不是java对象,因此为null	
	}
}

执行结果:
在这里插入图片描述
补充说明:

  1. sun.misc.Launcher源码:openjdk\jdk\src\share\classes\sun\misc目录
  2. 从上面的结果可以看出,并没有获取到ExtClassLoader的父ClassLoader,因为Bootstrap ClassLoader(启动类加载器)是用C语言实现的,getParent()找不到一个确定的返回父ClassLoader的方式,于是就返回null。

类加载器实例化过程:
通过java命令执行Java程序时,首先初始化JVM,产生Bootstrap ClassLoader(启动类加载器)——>Bootstrap ClassLoader自动加载Extended ClassLoader(扩展类加载器),并将其父ClassLoader设为Bootstrap Loader——>Bootstrap ClassLoader自动加载AppClass ClassLoader(系统类加载器),并将其父ClassLoader设为Extended ClassLoader。

类加载器的运行机制——双亲委派

一个类加载器要加载一个类时,首先会将此任务转给父类加载器,每个层次皆是如此,直至顶层的引导类加载器,如果引导类加载器能够在其搜索范围内完成加载,那么加载完成,反之交给下级尝试,以此类推,如果最终没有一个类加载器能够完成加载,那么就会抛出ClassNotFoundException异常,以上加载类的机制称为双亲委派机制。
我们查看ClassLoader抽象类中loadClass(String name, boolean resolve)方法的源码:

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded首先查看类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {//没有加载,执行if代码块内容
                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
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.如果依然没有找到,执行findClass找到class对象
                    long t1 = System.nanoTime();
                    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;
        }
    }

自定义类加载器

无法改变JDK自带类加载器的搜索路径,因此如果在程序运行时从特定搜索路径加载类,就要自定义类加载器,其定义步骤如下:

  • 自定义一个继承自ClassLoader抽象类的Java类;
  • 重写ClassLoader抽象类中findClass方法;
package com.zzu.test;

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

public class OtherClassLoader extends ClassLoader {
	
	private String classPath;
	public OtherClassLoader(String classPath) {
		this.classPath = classPath;
	}
	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		String className = classPath+"/"+name.replace(".", "/")+".class";//拼接完整路径
		InputStream inputStream;
		try {
			inputStream = new FileInputStream(className);//获取class文件创建输入流
			
			ByteArrayOutputStream  outputStream = new ByteArrayOutputStream();//创建字节数组输出流
			byte [] car = new byte[1024];
			int length =0;
			while((length=inputStream.read(car))!=-1) {
				outputStream.write(car, 0, length);
			}
			byte[] b = outputStream.toByteArray();//转换为字节码数组
			outputStream.flush();
			outputStream.close();
			return defineClass(name, b, 0, b.length);// 将字节码转化为Class对象
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;	
	}
}

测试:

import com.zzu.test.OtherClassLoader;

public class Test {

	public static void main(String[] args) {
		String className="com.zzu.vo.Student";//待加载的类全名
		String classPath = "D:/workspace/tt/bin";//待加载的类的路径
		OtherClassLoader classLoader = new OtherClassLoader(classPath);
		try {
			Class clazz = classLoader.findClass(className);//类加载
			System.out.println(clazz.getName());//加载成功,输出类名
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

执行结果:
在这里插入图片描述
自定义类加载器成功!

Class.forName()与ClassLoader区别

Java中Class.forName()和ClassLoader都可用来对类进行加载,但是它们之间也有不同的区别:

  • Class.forName()除了将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static块,还会执行给静态变量赋值的静态方法。
  • ClassLoader仅仅是将.class文件加载到JVM中,只有在创建对象时才会去执行类中的static块。
public class Student {

	static {
		System.out.println("静态代码块");
	}
}
public class Test {

	public static void main(String[] args) {
		try {
			Class.forName("com.jd.bolg.Student");
			System.out.println("------分割线------");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		ClassLoader classLoader = Test.class.getClassLoader();//任意获取一个类加载器
		try {
			classLoader.loadClass("com.jd.bolg.Student");//类加载
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

执行结果:
在这里插入图片描述
此外,也可以使用Class.forName(String name, boolean initialize, ClassLoader loader)加载类,如果initialize参数为true,则执行静态代码块,否则不执行静态代码块,此时只有创建对象时才会执行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值