JVM中的类加载

网友投稿 451 2022-05-30

类加载器

把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到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.启动类加载器

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

JVM中的类加载器

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

双亲委派描述:

如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,每一个层次的类加载器都是如果,因此所有的加载请求最终都应该传递到顶层的启动类加载器中

当父加载器反馈无法加载该类时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。

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

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

Java JVM

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:深入学习Java内存模型(Java面试问题)
下一篇:Laravel框架数据库CURD操作、连贯操作总结
相关文章