JVM中的类加载

网友投稿 478 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

JVM中的类加载器

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

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操作、连贯操作总结
相关文章