JVM中的类加载器

举报
波波烤鸭 发表于 2022/03/30 00:06:21 2022/03/30
【摘要】 类加载器   把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为类加载器。 ...

类加载器

  把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为类加载器。

自定义类加载器

  现在有个需求在项目中我们需要加载一个特定目录下的class文件【c:\tools\myClassLoader】,这时我们需要自己来定义特定的类加载器。

1.创建自定义类加载器

  继承ClassLoader后重写了findClass方法加载指定路径上的class,代码如下:

import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * 自定义类加载器
 * @author 波波烤鸭
 * @email dengpbs@163.com
 *
 */
public class MyClassLoader extends ClassLoader {
	// 加载的路径
	private String path;

	public MyClassLoader(String path) {
		super();
		this.path = path;
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		try {
			byte[] result = getClass(name);
			if (result == null) {
				throw new ClassNotFoundException();
			} else {
				// 将字节流转换为Class对象
				return defineClass(name, result, 0, result.length);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	// 加载class为字节数组
	private byte[] getClass(String name) {
		try {
			return Files.readAllBytes(Paths.get(path));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

2.测试

指定目录下存放编译好的class文件,注意用相关的jdk版本编译
在这里插入图片描述

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
	MyClassLoader myLoader = new MyClassLoader("C:\\tools\\myClassLoader\\User.class");
	Class clazz = myLoader.loadClass("com.dpb.pojo.User");
	Object object = clazz.newInstance();
	System.out.println(object);
	System.out.println(object.getClass().getClassLoader());
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

输出结果

com.dpb.pojo.User@4e25154f
com.dpb.loader.MyClassLoader@6d06d69c

  
 
  • 1
  • 2

实现了加载特定目录下的class文件

ClassLoader

  上面的代码虽然实现加载特定目录下的class文件,但这么执行的原因是什么呢?要了解这个我们需要来具体看下ClassLoader的源代码。代码比较多,截取了核心的代码

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);
         // 获取类加载器 如果为null 本方法就结束了
         if (c == null) {
             long t0 = System.nanoTime();
             try {
             	 // 如果parent为null
                 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
             }
			 // 如果所有的父加载器都没有找到Class
             if (c == null) {
                 // If still not found, then invoke findClass in order
                 // to find the class.
                 long t1 = System.nanoTime();
                 // 就调用自身的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;
     }
 }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
// 本方法并没有去查找Class,本方法留给子类去重写的
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

  
 
  • 1
  • 2
  • 3
  • 4

  所以如果我们需要加载特定的Class文件的时候只需要重写findClass方法即可,而不用去重写loadClass方法。

双亲委派模型

  通过ClassLoader中的loadClass方法我们发现类加载器加类的时候有既定的原则,而且系统提供的类加载器好像也不止一个,我们就来说下这块。系统给我们提供了三个类加载器,如下

序号 类加载 说明
1 启动类加载器
Bootstrap ClassLoader
加载<JAVA_HOME> \lib目录下或-Xbootclasspath指定路径下能被虚拟机识别的类库加载到虚拟机中(rj.jar) ,无法被java程序直接是使用
2 扩展类加载器
Extension ClassLoader
负责加载<JAVA_HOME> \lib\ext目录中或者被java.ext.dirs指定的目录下的类库,程序员可以直接使用该加载器
3 应用程序类加载器
Application ClassLoader
也称系统类加载器,负责加载用户类路径上所指定的类库,一般是程序默认的类加载器

在这里插入图片描述

1.启动类加载器

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

  
 
  • 1
  • 2
  • 3
  • 4

启动类加载器我们无法通过程序获取,所以打印结果为null,可是加载资源的路径可以获取。

BootStrap:null
C:\Program Files\Java\jre1.8.0_144\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_144\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_144\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_144\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_144\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_144\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_144\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_144\classes

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.扩展类加载器

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

  
 
  • 1
  • 2
  • 3

加载路径如下:

C:\Program Files\Java\jre1.8.0_144\lib\ext;
C:\Windows\Sun\Java\lib\ext

  
 
  • 1
  • 2

我们也可以将自己的文件打成jar包放到扩展目录下,也会被扩展类加载器加载。

3.系统类加载器

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

  
 
  • 1
  • 2
  • 3

加载路径

C:\Users\dengp\Desktop\共享文件\Java1112\workspace\others\FreemarkerDemo\target\classes;
C:\Users\dengp\.m2\repository\org\springframework\spring-context\4.3.21.RELEASE\spring-context-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-aop\4.3.21.RELEASE\spring-aop-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-beans\4.3.21.RELEASE\spring-beans-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-core\4.3.21.RELEASE\spring-core-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-expression\4.3.21.RELEASE\spring-expression-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-webmvc\4.3.21.RELEASE\spring-webmvc-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-web\4.3.21.RELEASE\spring-web-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\freemarker\freemarker\2.3.28\freemarker-2.3.28.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-context-support\4.3.21.RELEASE\spring-context-support-4.3.21.RELEASE.jar

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

双亲委派描述:

  1. 如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,每一个层次的类加载器都是如果,因此所有的加载请求最终都应该传递到顶层的启动类加载器中
  2. 当父加载器反馈无法加载该类时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。

弄清楚这个委派模型后再去看loadClass方法中的逻辑应该就比较容易了。

参考《深入理解Java虚拟机》

文章来源: dpb-bobokaoya-sm.blog.csdn.net,作者:波波烤鸭,版权归原作者所有,如需转载,请联系作者。

原文链接:dpb-bobokaoya-sm.blog.csdn.net/article/details/88386495

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。