这篇文章运用简单易懂的例子给大家介绍java中类加载器的使用方法,代码非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
概述
Java类加载器( 英语:Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到 Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。
获得ClassLoader的途径
1. 获得当前类的ClassLoader
clazz.getClassLoader()
2. 获得当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
3. 获得系统的ClassLoader
ClassLoader.getSystemClassLoader()
4. 获得调用者的ClassLoader
DriverManager.getCallerClassLoader
ClassLoader源码解析
代码一:
public class Test12 {
public static void main(String[] args) {
String[] strings = new String[6];
System.out.println(strings.getClass().getClassLoader());
// 运行结果:null
Test12[] test12s = new Test12[1];
System.out.println(test12s.getClass().getClassLoader());
// 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2
int[] ints = new int[2];
System.out.println(ints.getClass().getClassLoader());
// 运行结果:null
}
}
loadClass方法
loadClass的源码如下, loadClass方法加载拥有指定的二进制名称的Class,默认按照如下顺序寻找类:
a)调用findLoadedClass(String)检查这个类是否被加载
b)调用父类加载器的loadClass方法,如果父类加载器为null,就会调用启动类加载器
c)调用findClass(String)方法寻找
使用上述步骤如果类被找到且resolve为true,就会去调用resolveClass(Class)方法
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) {
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.
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;
}
}
findClass方法
findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
defineClass方法
defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
自定义类加载器
/**
* 继承了ClassLoader,这是一个自定义的类加载器
* @author 夜的那种黑丶
*/
public class ClassLoaderTest extends ClassLoader {
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
Class<?> clazz = loader.loadClass("classloader.Test01");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}
//------------------------------以上为测试代码---------------------------------
/**
* 类加载器名称,标识作用
*/
private String classLoaderName;
/**
* 从磁盘读物字节码文件的扩展名
*/
private String fileExtension = ".class";
/**
* 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器
* @param classLoaderName 类加载器名称
*/
private ClassLoaderTest(String classLoaderName) {
// 将系统类加载器当做该类加载器的父加载器
super();
this.classLoaderName = classLoaderName;
}
/**
* 创建一个类加载器对象,显示指定该类加载器的父加载器
* 前提是需要有一个类加载器作为父加载器
* @param parent 父加载器
* @param classLoaderName 类加载器名称
*/
private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
// 显示指定该类加载器的父加载器
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制
* 该方法会在检查完父类加载器之后被loadClass方法调用
* 默认返回ClassNotFoundException异常
* @param className 类名
* @return Class的实例
* @throws ClassNotFoundException 如果类不能被找到,抛出此异常
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = this.loadClassData(className);
/*
* 通过defineClass方法将字节数组转换为Class
* defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析
*/
return this.defineClass(className, data, 0 , data.length);
}
/**
* io操作,根据类名找到对应文件,返回class文件的二进制信息
* @param className 类名
* @return class文件的二进制信息
* @throws ClassNotFoundException 如果类不能被找到,抛出此异常
*/
private byte[] loadClassData(String className) throws ClassNotFoundException {
InputStream inputStream = null;
byte[] data;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
this.classLoaderName = this.classLoaderName.replace(".", "/");
inputStream = new FileInputStream(new File(className + this.fileExtension));
byteArrayOutputStream = new ByteArrayOutputStream();
int ch;
while (-1 != (ch = inputStream.read())) {
byteArrayOutputStream.write(ch);
}
data = byteArrayOutputStream.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
}
以上是一段自定义类加载器的代码,我们执行这段代码
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)
将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。
改造以下代码,添加一个path属性用来指定类加载位置:
public class ClassLoaderTest extends ClassLoader {
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz);
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}
//------------------------------以上为测试代码---------------------------------
/**
* 从指定路径加载
*/
private String path;
......
/**
* io操作,根据类名找到对应文件,返回class文件的二进制信息
* @param className 类名
* @return class文件的二进制信息
* @throws ClassNotFoundException 如果类不能被找到,抛出此异常
*/
private byte[] loadClassData(String className) throws ClassNotFoundException {
InputStream inputStream = null;
byte[] data;
ByteArrayOutputStream byteArrayOutputStream = null;
className = className.replace(".", "/");
try {
this.classLoaderName = this.classLoaderName.replace(".", "/");
inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
byteArrayOutputStream = new ByteArrayOutputStream();
int ch;
while (-1 != (ch = inputStream.read())) {
byteArrayOutputStream.write(ch);
}
data = byteArrayOutputStream.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public void setPath(String path) {
this.path = path;
}
}
运行一下
class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
修改一下测试代码,并删除工程下的Test01.class文件
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/桌面/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz);
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}
运行一下
class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a
分析
改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。
再来测试一块代码
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object.getClass().getClassLoader());
ClassLoaderTest loader2 = new ClassLoaderTest("loader");
loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
Class<?> clazz2 = loader2.loadClass("classloader.Test01");
System.out.println("class:" + clazz2.hashCode());
Object object2 = clazz2.newInstance();
System.out.println(object2.getClass().getClassLoader());
}
结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
再改造一下
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/桌面/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object.getClass().getClassLoader());
ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
loader2.setPath("/home/fanxuan/桌面/");
Class<?> clazz2 = loader2.loadClass("classloader.Test01");
System.out.println("class:" + clazz2.hashCode());
Object object2 = clazz2.newInstance();
System.out.println(object2.getClass().getClassLoader());
}
运行结果
class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7
ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。
我们可以通过设置父类加载器来让loader和loader2处于同一命名空间
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/桌面/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object.getClass().getClassLoader());
ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
loader2.setPath("/home/fanxuan/桌面/");
Class<?> clazz2 = loader2.loadClass("classloader.Test01");
System.out.println("class:" + clazz2.hashCode());
Object object2 = clazz2.newInstance();
System.out.println(object2.getClass().getClassLoader());
}
运行结果
class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a
扩展:命名空间
1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
关于java中类加载器的使用方法就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。