JVM类加载器之自定义类加载器

迷南。 2022-04-18 06:50 466阅读 0赞
  • jvm目录

  • 废话不多说,自定义的类加载器都要继承于java.lang.ClassLoader类,它定义了默认的加载的规范,并提供了一些方法由我们重写而实现自己的加载逻辑。所以我们要先了解一下ClassLoader类。

java.lang.ClassLoader


  • ClassLoader为我们提供了几个方法,其中我们可能比较会需要重写的是findClass()与loadClass()方法。现在我们来看一下ClassLoader的loadClass方法是怎么实现。
  • protected Class<?> loadClass(String name, boolean resolve)

    1. throws ClassNotFoundException
    2. {
    3. synchronized (getClassLoadingLock(name)) {
    4. // First, check if the class has already been loaded
    5. Class<?> c = findLoadedClass(name);
    6. if (c == null) {
    7. long t0 = System.nanoTime();
    8. try {
    9. if (parent != null) {
    10. c = parent.loadClass(name, false);
    11. } else {
    12. c = findBootstrapClassOrNull(name);
    13. }
    14. } catch (ClassNotFoundException e) {
    15. // ClassNotFoundException thrown if class not found
    16. // from the non-null parent class loader
    17. }
    18. if (c == null) {
    19. // If still not found, then invoke findClass in order
    20. // to find the class.
    21. long t1 = System.nanoTime();
    22. c = findClass(name);
    23. // this is the defining class loader; record the stats
    24. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    25. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    26. sun.misc.PerfCounter.getFindClasses().increment();
    27. }
    28. }
    29. if (resolve) {
    30. resolveClass(c);
    31. }
    32. return c;
    33. }
    34. }
  • 如上文代码,loadClass()是类加载器加载类的入口,也就是说loadClass是加载类的第一步。loadClass方法也是双亲委派模型的实现过程。如果你需要打破双亲委派模型,就去重写loadClass方法,如果不需要就直接使用ClassLoader的loadClass方法即可。

  • name就是需要加载的类的“标识”,它的作用是能让加载器找到需要加载的二进制文件。比如name值为”java.lang.Object”之类的。然后我们一步一步的看一下代码,是如何实现双亲委派模型的。
  • 第一步是加锁,一个类只允许被同一个加载器加载一次,避免多线程多次加载问题。
  • 第二步根据name查找一下是否已经加载过了,如果加载过了直接放回该类的Class对象。然后根据判断是否需要resolveClass方法。

    • resolveClass()是执行加载过程中的“连接”(验证,准备,解析)过程。通过源代码,我们可以得知这个方法不可以被重写,实际上连接的过程也是完全由虚拟机处理的,我们不能也不可能进行干预。
  • 如果name对应的类加载器不曾加载过,则现在需要加载。首先是查找当前类是否有父类,有父类应该交由父类处理,并层层传递上去,直到没有父类则交给启动类加载器类加载器(Bootstrap ClassLoader)进行加载。如上代码,如果类加载器无法加载则抛出ClassNotFoundException 异常,返回给子类加载器执行,如此层层传递回来。这样就能保证同一个类一定是由同一个类加载器加载的。这就是双亲委派模型。
  • 当传递回本类加载的时候,说明“父类”的加载器们都无法执行这个类。然后就是通过findClass根据name去寻找到对应的二进制字节流信息加载到方法区中,并返回一个java.lang.Class对象,来代表的这个类。这也就是我们需要重写的方法findClass(),findClass对应的就是类加载过程中的第一步(加载过程)。而我们自定义类加载器主要的也是实现这个过程,来控制二进制字节流的加载方式。
  • 如果都找不到,则将ClassNotFoundException 异常抛出到外面去了。

所以重写findClass方法是为了来控制二进制字节流的加载方式,比如从数据库中,或从其它文件,或者线上获取,再或者是为了实现对重要字节流的加密解密。

而重写loadClass方法是为了破坏双亲委派机制,实现自己加载机制。


  • 接下来提供一个自定义加载的示例代码

    public class MyClassLoader extends ClassLoader {

    1. protected Class<?> findClass(String name) throws ClassNotFoundException
    2. {
    3. File file = new File("D:/"+name.replaceAll(".","/")+".class");
    4. try{
    5. byte[] bytes = getClassBytes(file);
    6. Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
    7. return c;
    8. }
    9. catch (Exception e)
    10. {
    11. e.printStackTrace();
    12. }
    13. return super.findClass(name);
    14. }
    15. private byte[] getClassBytes(File file) throws Exception
    16. {
    17. // 这里要读入.class的字节,因此要使用字节流
    18. FileInputStream fis = new FileInputStream(file);
    19. FileChannel fc = fis.getChannel();
    20. ByteArrayOutputStream baos = new ByteArrayOutputStream();
    21. WritableByteChannel wbc = Channels.newChannel(baos);
    22. ByteBuffer by = ByteBuffer.allocate(1024);
    23. while (true){
    24. int i = fc.read(by);
    25. if (i == 0 || i == -1)
    26. break;
    27. by.flip();
    28. wbc.write(by);
    29. by.clear();
    30. }
    31. fis.close();
    32. return baos.toByteArray();
    33. }

    }

  • 主程序测试

    MyClassLoader myClassLoader = new MyClassLoader();
    Class<?> testInfoClass= Class.forName(“spongebob.demo.TestInfo”, true, myClassLoader);
    Object testInfo= testInfoClass.newInstance();

    System.out.println(testInfo);
    System.out.println(testInfo.getClass().getClassLoader());

  • 这样我们就实现了自定义类加载器,并成功加载了在D://spongebob/demo/路径下预先编译好的TestInfo.class文件进入虚拟机中。

  • findClass之中主要有两行代码,getClassBytes是为了获取数据的二进制字节流。
  • defineClass是由系统提供且是必须的调用的,它是将二进制流字节转化为方法区中类的数据存储格式,并返回这个类的java.lang.Class对象。

发表评论

表情:
评论列表 (有 0 条评论,466人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java jvm-定义

    除了可以用系统默认的类加载器,我们还可以用实现自己的类加载器,类加载器实现步骤如下: 1.定义一个类继承ClassLoader 2.重写findClass方法,用来查找具

    相关 定义

    loadClass方法实现了双亲委派模型。 1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。 2. 如果此类没有加载过,那么,再判