原文

首先来了解一下字节码和class文件的区别:

我们知道,新建一个java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java工程的bin目录下)指定的目录下的.class文件,类加载需要将.class文件导入到硬盘中,经过一些处理之后变成字节码在加载到内存中。

下面来看一下简单的例子:

  1. package com.loadclass.demo;
  2. import java.util.Date;
  3. import java.util.List;
  4. /**
  5. * 测试类
  6. * @author Administrator
  7. */
  8. public class ClassLoaderTest {
  9. @SuppressWarnings("rawtypes")
  10. public static void main(String[] args){
  11. //输出ClassLoaderText的类加载器名称
  12. System.out.println("ClassLoaderText类的加载器的名称:"+ClassLoaderTest.class.getClassLoader().getClass().getName());
  13. System.out.println("System类的加载器的名称:"+System.class.getClassLoader());
  14. System.out.println("List类的加载器的名称:"+List.class.getClassLoader());
  15. ClassLoader cl = ClassLoaderTest.class.getClassLoader();
  16. while(cl != null){
  17. System.out.print(cl.getClass().getName()+"->");
  18. cl = cl.getParent();
  19. }
  20. System.out.println(cl);
  21. }
  22. }

输出结果:

可以看到,ClassLoaderTest类时由AppClassLoader类加载器加载的。下面就来了解一下JVM中的各个类加载器,同时来解释一下运行的结果。

Java虚拟机中类加载器:

Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类:

BootStrap,ExtClassLoader,AppClassLoader

类加载器也是Java类,因为Java类的类加载器本身也是要被类加载器加载的,显然必须有第一个类加载器不是Java类,这个正是BootStrap,使用C/C++代码写的,已经封装到JVM内核中了,而ExtClassLoader和AppClassLoader是Java类。

看一下类加载器的属性结构图:

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象的时候,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载

类加载器的委托机制:

当Java虚拟机要加载第一个类的时候,到底派出哪个类加载器去加载呢?

(1). 首先当前线程的类加载器去加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader cl);方法,可以获取/指定本线程中的类加载器)

(2). 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B

(3). 还可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类

每个类加载器加载类时,又先委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则会抛出ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild()方法。例如:如上图所示: MyClassLoader->AppClassLoader->Ext->ClassLoader->BootStrap.自定定义的MyClassLoader1首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的MyClassLoader1类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。

对着类加载器的层次结构图和委托加载原理,解释先前的运行的结果

因为System类,List,Map等这样的系统提供jar类都在rt.jar中,所以由BootStrap类加载器加载,因为BootStrap是祖先类,不是Java编写的,所以打印出class为null

对于ClassLoaderTest类的加载过程,打印结果也是很清楚的。

现在再来做个试验来验证上面的结论:

首先将ClassLoaderTest.java打包成.jar文件(这个步骤就不说了吧,很简单的)

然后将.jar文件拷贝到Java的安装目录中的Java/jre7/lib/ext/目录下

这时候你在运行ClassLoaderTest类,结果如下:

这时候就发现了ClassLoaderTest的类加载器变成了ExtClassLoader,这时候就说明了上面的结论是正确的,因为ExtClassLoader加载jre/ext/*.jar,首先AppClassLoader类加载器发请求给ExtClassLoader,然后ExtClassLoader发请求给BootStrap,但是BootStrap没有找到ClassLoaderTest类,所以交给ExtClassLoader处理,这时候ExtClassLoader在my_lib.jar中找到了ClassLoaderTest类,所以就把它加载了,然后结束了。

其实采用这种树形的类加载机制的好处就在于:

能够很好的统一管理类加载,首先交给上级,如果上级有了,就加载,这样如果之前已经加载过的类,这时候在来加载它的时候只要拿过来用就可以了,无需二次加载了

下面来看一下怎么定义我们自己的一个类加载器MyClassLoader:

自己可以定义类加载器,要将自己定义的类加载器挂载到系统类加载器树上,在ClassLoader的构造方法中可以指定parent,没有指定的话,就使用默认的parent

这里看一下默认的parent是使用getSystemClassLoader方法获取的,这个方法的源码没有找到,所以只能通过代码来测试一下了

  1. System.out.println("默认的类加载器:"+ClassLoaderTest.class.getClassLoader().getSystemClassLoader());

输入结果为:

所以默认的都是将自定义的类加载器挂载到系统类加载器的最低端AppClassLoader,这个也是很合理的。

自定义的类加载器必须继承抽象类ClassLoader然后重写findClass方法,其实他内部还有一个loadClass方法和defineClass方法,这两个方法的作用是:

loadClass方法的源代码:

  1. public Class<?> loadClass(String name) throws ClassNotFoundException {
  2. return loadClass(name, false);
  3. }

再来看一下loadClass(name,false)方法的源代码:

  1. protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
  2. //加上锁,同步处理,因为可能是多线程在加载类
  3. synchronized (getClassLoadingLock(name)) {
  4. //检查,是否该类已经加载过了,如果加载过了,就不加载了
  5. Class c = findLoadedClass(name);
  6. if (c == null) {
  7. long t0 = System.nanoTime();
  8. try {
  9. //如果自定义的类加载器的parent不为null,就调用parent的loadClass进行加载类
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. //如果自定义的类加载器的parent为null,就调用findBootstrapClass方法查找类,就是Bootstrap类加载器
  14. c = findBootstrapClassOrNull(name);
  15. }
  16. } catch (ClassNotFoundException e) {
  17. // ClassNotFoundException thrown if class not found
  18. // from the non-null parent class loader
  19. }
  20. if (c == null) {
  21. // If still not found, then invoke findClass in order
  22. // to find the class.
  23. long t1 = System.nanoTime();
  24. //如果parent加载类失败,就调用自己的findClass方法进行类加载
  25. c = findClass(name);
  26. // this is the defining class loader; record the stats
  27. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  28. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  29. sun.misc.PerfCounter.getFindClasses().increment();
  30. }
  31. }
  32. if (resolve) {
  33. resolveClass(c);
  34. }
  35. return c;
  36. }
  37. }

在loadClass代码中也可以看到类加载机制的原理,这里还有这个方法findBootstrapClassOrNull,看一下源代码:

  1. private Class findBootstrapClassOrNull(String name)
  2. {
  3. if (!checkName(name)) return null;
  4. return findBootstrapClass(name);
  5. }

就是检查一下name是否是否正确,然后调用findBootstrapClass方法,但是findBootstrapClass方法是个native本地方法,看不到源代码了,但是可以猜测是用Bootstrap类加载器进行加载类的,这个方法我们也不能重写,因为如果重写了这个方法的话,就会破坏这种委托机制,我们还要自己写一个委托机制,很是蛋疼的。

defineClass这个方法很简单就是将class文件的字节数组编程一个class对象,这个方法肯定不能重写,内部实现是在C/C++代码中实现的

findClass这个方法就是根据name来查找到class文件,在loadClass方法中用到,所以我们只能重写这个方法了,只要在这个方法中找到class文件,再将它用defineClass方法返回一个Class对象即可。

这三个方法的执行流程是:每个类加载器:loadClass->findClass->defineClass

 

前期的知识了解后现在就来实现了

首先来看一下需要加载的一个类:ClassLoaderAttachment.java:

  1. package com.loadclass.demo;
  2. import java.util.Date;
  3. /**
  4. * 加载类
  5. * @author Administrator
  6. */
  7. public class ClassLoaderAttachment extends Date{
  8. private static final long serialVersionUID = 8627644427315706176L;
  9. //打印数据
  10. @Override
  11. public String toString(){
  12. return "Hello ClassLoader!";
  13. }
  14. }

这个类中输出一段话即可:编译成ClassLoaderAttachment.class

再来看一下自定义的MyClassLoader.java:

  1. package com.loadclass.demo;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.InputStream;
  6. import java.io.OutputStream;
  7. /**
  8. * 自定义的类加载器
  9. * @author Administrator
  10. */
  11. public class MyClassLoader extends ClassLoader{
  12. //需要加载类.class文件的目录
  13. private String classDir;
  14. //无参的构造方法,用于class.newInstance()构造对象使用
  15. public MyClassLoader(){
  16. }
  17. public MyClassLoader(String classDir){
  18. this.classDir = classDir;
  19. }
  20. @SuppressWarnings("deprecation")
  21. @Override
  22. protected Class<?> findClass(String name) throws ClassNotFoundException {
  23. //class文件的路径
  24. String classPathFile = classDir + "/" + name + ".class";
  25. try {
  26. //将class文件进行解密
  27. FileInputStream fis = new FileInputStream(classPathFile);
  28. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  29. encodeAndDecode(fis,bos);
  30. byte[] classByte = bos.toByteArray();
  31. //将字节流变成一个class
  32. return defineClass(classByte,0,classByte.length);
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. return super.findClass(name);
  37. }
  38. //测试,先将ClassLoaderAttachment.class文件加密写到工程的class_temp目录下
  39. public static void main(String[] args) throws Exception{
  40. //配置运行参数
  41. String srcPath = args[0];//ClassLoaderAttachment.class原路径
  42. String desPath = args[1];//ClassLoaderAttachment.class输出的路径
  43. String desFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
  44. String desPathFile = desPath + "/" + desFileName;
  45. FileInputStream fis = new FileInputStream(srcPath);
  46. FileOutputStream fos = new FileOutputStream(desPathFile);
  47. //将class进行加密
  48. encodeAndDecode(fis,fos);
  49. fis.close();
  50. fos.close();
  51. }
  52. /**
  53. * 加密和解密算法
  54. * @param is
  55. * @param os
  56. * @throws Exception
  57. */
  58. private static void encodeAndDecode(InputStream is,OutputStream os) throws Exception{
  59. int bytes = -1;
  60. while((bytes = is.read())!= -1){
  61. bytes = bytes ^ 0xff;//和0xff进行异或处理
  62. os.write(bytes);
  63. }
  64. }
  65. }

这个类中定义了一个加密和解密的算法,很简单的,就是将字节和oxff异或一下即可,而且这个算法是加密和解密的都可以用,很是神奇呀!

当然我们还要先做一个操作就是,将ClassLoaderAttachment.class加密后的文件存起来,也就是在main方法中执行的,这里我是在项目中新建一个class_temp文件夹用来皴法加密后的class文件:

同时采用的是参数的形式来进行赋值的,所以在运行的MyClassLoader的时候要进行输入参数的配置:右击MyClassLoader->run as -> run configurations

第一个参数是ClassLoaderAttachment.class文件的源路径,第二个参数是加密后存放的目录,运行MyClassLoader之后,刷新class_temp文件夹,出现了ClassLoaderAttachment.class,这个是加密后的class文件。

下面来看一下测试类:

  1. package com.loadclass.demo;
  2. import java.util.Date;
  3. import java.util.List;
  4. /**
  5. * 测试类
  6. * @author Administrator
  7. */
  8. public class ClassLoaderTest {
  9. @SuppressWarnings("rawtypes")
  10. public static void main(String[] args){
  11. //输出ClassLoaderText的类加载器名称
  12. System.out.println("ClassLoaderText类的加载器的名称:"+ClassLoaderTest.class.getClassLoader().getClass().getName());
  13. System.out.println("System类的加载器的名称:"+System.class.getClassLoader());
  14. System.out.println("List类的加载器的名称:"+List.class.getClassLoader());
  15. System.out.println("默认的类加载器:"+ClassLoaderTest.class.getClassLoader().getSystemClassLoader());
  16. ClassLoader cl = ClassLoaderTest.class.getClassLoader();
  17. while(cl != null){
  18. System.out.print(cl.getClass().getName()+"->");
  19. cl = cl.getParent();
  20. }
  21. System.out.println(cl);
  22. try {
  23. Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
  24. Date date = (Date) classDate.newInstance();
  25. //输出ClassLoaderAttachment类的加载器名称
  26. System.out.println("ClassLoader:"+date.getClass().getClassLoader().getClass().getName());
  27. System.out.println(date);
  28. } catch (Exception e1) {
  29. e1.printStackTrace();
  30. }
  31. }
  32. }

运行ClassLoaderTest类,运行结果如下:

ClassLoaderAttachment类的加载器是我们自己定义的类加载器MyClassLoader,同时也输出了Hello ClassLoader字段

到此不要以为结束了,这里还有很多的问题呀,看一下问题的结果是没有问题,但是这里面还有很多的东西需要去理解的,首先来看一下,按照我们之前说的类加载机制,应该是先交给父级的类加载器,AppClassLoader->ExtClassLoader->BootStrap,ExtClassLoader和BootStrap没有找到ClassLoaderAttach.class,但是AppClassLoader类加载器应该能找到呀,可以为什么也没有找到呢?这时因为loadClass方法在使用系统类加载器的时候需要传递全称(包括包名),我们传递ClassLoaderAttachment的话,AppClassLoader也是没有找到这个ClassLoaderAttachment,所以还是MyClassLoader处理了,不信的话可以试一下:

现在将

  1. Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");

改成:

  1. Class classDate = new MyClassLoader("class_temp").loadClass("com.loadclass.demo.ClassLoaderAttachment");

结果运行:

这时候的加载器是AppClassLoader了,所以要注意loadClass方法传递的参数

到这里我们貌似还没有测试到我们加密后的class文件,我们现在将工程目录中的ClassLoaderAttachment.class删除,将class_temp中加密的ClassLoaderAttachment.class拷贝过去,然后再运行:

这时候就会报错了,不合适的魔数错误(class文件的前六个字节是个魔数用来标识class文件的),这时候就对了,因为ClassLoaderAttachment.class使我们加密后的class文件,AppClassLoader是不认识的,所以报这个错误了,只有用我们自己定义的类加载器来进行解密才可以正确的访问的。到这里总算是说完了,搞了一上午,头都写大了,很是麻烦呀!

注意的两个问题:

1.可能在测试的过程中有这样的情况就是ClassLoaderTest类并没有执行,这个是因为在第一个测试的时候,将ClassLoaderTest类打成.jar放到jre目录中了,所以你后续修改ClassLoaderTest类的话,运行没有效果,因为它加载的类还是jre中的jar中的ClassLoaderTest类,所以你应该将jre中的jar删除即可。

2.就是ClassLoaderAttachment只要保存就会编译成.class文件,所以你在拷贝ClassLoaderAttachment.class文件的时候要注意了。

java ClassLoader类加载器的更多相关文章

  1. ClassLoader类加载器 & Java类加载机制 & 破坏双亲委托机制

    ClassLoader类加载器 Java 中的类加载器大致可以分成两类: 一类是系统提供的: 引导类加载器(Bootstrap classloader):它用来加载 Java 的核心库(如rt.jar ...

  2. 进阶Java编程(11)ClassLoader类加载器【待完成】

    1,ClassLoader类加载器简介 在Java里面提供一个系统的环境变量:ClassPath,这个属性的作用主要是在JVM进程启动的时候进行类加载路径的定义,在JVM里面可以根据类加载器而后进行指 ...

  3. Java安全之 ClassLoader类加载器

    Java安全之 ClassLoader类加载器 0x00 前言 前面这里抛出一个问题,Java到底是什么类型的编程语言?是编译型?还是解释型?在这个问题是其实一直都都有疑惑,如果说是解释型语言的话,那 ...

  4. java自定义类加载器

    前言 java反射,最常用的Class.forName()方法.做毕设的时候,接收到代码字符串,通过 JavaCompiler将代码字符串生成A.class文件(存放在classpath下,也就是ec ...

  5. Java虚拟机类加载器及双亲委派机制

    所谓的类加载器(Class Loader)就是加载Java类到Java虚拟机中的,前面<面试官,不要再问我"Java虚拟机类加载机制"了>中已经介绍了具体加载class ...

  6. 深入探讨java的类加载器

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一.它使得 Java 类可以被动态加载到 Java 虚拟机中并执行.类加载器从 JDK 1.0 就出现了,最初是为了满足 Ja ...

  7. 万万没想到,面试中,连 ClassLoader类加载器 也能问出这么多问题…..

    1.类加载过程 类加载时机 「加载」 将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存上创建一个java.lang.Class对象用来封装类在方法区内的数据 ...

  8. java 中类加载器

    jar 运行过程和类加载机制有关,而类加载机制又和我们自定义的类加载器有关,现在我们先来了解一下双亲委派模式. java 中类加载器分为三个: BootstrapClassLoader 负责加载 ${ ...

  9. 分析Java的类加载器与ClassLoader(二):classpath与查找类字节码的顺序,分析ExtClassLoader与AppClassLoader的源码

    先回顾一下classpath classpath的作用: classpath的作用是指定查找类的路径:当使用java命令执行一个类(类中的main方法)时,会从classpath中进行查找这个类. 指 ...

随机推荐

  1. log4j 2使用properties文件进行配置

    网上不少文章给的都是用xml进行配置,也会提到无法使用properties文件对log4j进行配置,但那应该只是在他们写文章的时候才是如此,最新的2.8.2版本经过我试验后是可以做到的当然该文件最好放 ...

  2. [转] VS2015中跑OpenGL红宝书第八版的第一章示例代码,运行

    Ori Article Link OpenGL的东西快忘光了,把角落的第八版红宝书拿出来复习一下 从书中的地址下了个示例代码结果新系统(Win10+VS2015)各种跑不起来,懊恼之后在网上疯狂搜索资 ...

  3. 聊聊、Zookeeper Linux 集群服务

    今天是平安夜,先祝大家平安夜快乐.这篇文章我们来谈谈 Zookeeper Linux 集群. 为什么要集群呢?因为一台服务不够.集群是为了系统扩容,系统稳定.一台服务挂了,没关系,我还有其他的服务.集 ...

  4. UIView和UIImageView 旋转消除锯齿方法

    方法一: calendarImageView_ =[[UIImageView alloc] initWithFrame:CGRectMake(3,3,60,72)];     calendarImag ...

  5. Dedecms 数据库结构分析

    本文主要是为了今后对Dedecms做二次开发所写.安装后dedecms的数据库结构,如(图1)所示, 安装后的dedecms一共有 86 张数据表. 主要数据结构表 dede_addonarticle ...

  6. hdu1017(C++)

    这个题目很水,但是卡了格式和m=0的情况,wa了好多次,题目只给出N=1,感觉没说清楚 #include<iostream>using namespace std;int main(){ ...

  7. 测试整合之王Unitils

    16.4.1  Unitils概述(1) Unitils测试框架目的是让单元测试变得更加容易和可维护.Unitils构建在DbUnit与EasyMock项目之上并与JUnit和TestNG相结合.支持 ...

  8. RR调度(Round-robin scheduling)简单介绍

    在RR调度策略下,一个线程会一直运行.直到: 自愿放弃控制权 被更高优先级的线程抢占 时间片用完 例如以下图所看到的,A在用完自己的时间片后,将CPU运行权让给线程B.于是A离开Read队列,而B进入 ...

  9. apue学习笔记(第四章 文件和目录)

    本章将描述文件系统的其他特性和文件的性质. 函数stat.fstat.fstatat和lstat #include <sys/stat.h> int stat(const char *re ...

  10. 在 Selenium 中让 PhantomJS 执行它的 API

    from selenium import webdriver driver = webdriver.PhantomJS() script = "var page = this; page.o ...