Java自定义类加载器以及加载类及其父类的过程

最近学习JVM的时候,对类的加载原理有了一定的认识,但是涉及到具体例子时还是没有分析清楚,重写loadClass()方法和重写findClass()方法。找到一篇不错的博客,该博主从对问题的思考到源码跟踪,以及测试都是非常不错的。

转载自:https://www.cnblogs.com/pfxiong/p/4118445.html

//示例:
 2 package com.csair.soc;
 3 
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 
 7 public class MyClassLoader1   extends ClassLoader{
 8         @Override
 9         public Class loadClass(String name) throws ClassNotFoundException{
10                try{
11                      String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";
12                      InputStream is = this.getClass().getResourceAsStream(fileName);
13                       byte[] b = new byte[is.available()];
14                      is.read(b);
15                       return defineClass(name, b, 0, b.length );
16               } catch(IOException e){
17                       throw new ClassNotFoundException(name);
18               }
19        }
20 }
21 
22 
23 package com.csair.soc;
24 public class ClassLoaderTest {
25         public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
26               MyClassLoader1 myLoader = new MyClassLoader1();
27               Object obj = myLoader.loadClass("com.csair.soc.ClassLoaderTest" ).newInstance();
28               System. out.println(obj.getClass());
29               System. out.println(obj.getClass().getClassLoader());
30               System. out.println(obj instanceof com.csair.soc.ClassLoaderTest);
31        }
32 }

输出结果?

Exception in thread "main" java.lang.NullPointerException

       at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:13)

       at java.lang.ClassLoader.defineClass1( Native Method)

       at java.lang.ClassLoader.defineClassCond( ClassLoader.java:631)

       at java.lang.ClassLoader.defineClass( ClassLoader.java:615)

       at java.lang.ClassLoader.defineClass( ClassLoader.java:465)

       at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:15)

       at com.csair.soc.ClassLoaderTest.main( ClassLoaderTest.java:7)

为什么在自定义的MyClassLoader1中Override   loadClass会失败?ClassLoaderTest文件在当前目录下,为什么还会报空指针异常?

在loadClass下,添加以下代码做测试。

System.out.println(name);

结果:

com.csair.soc.ClassLoaderTest

java.lang.Object

Exception in thread "main" java.lang.NullPointerException

       at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:13)

       at java.lang.ClassLoader.defineClass1( Native Method)

       at java.lang.ClassLoader.defineClassCond( ClassLoader.java:631)

       at java.lang.ClassLoader.defineClass( ClassLoader.java:615)

       at java.lang.ClassLoader.defineClass( ClassLoader.java:465)

       at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:15)

       at com.csair.soc.ClassLoaderTest.main( ClassLoaderTest.java:7)

为什么要加载两次?要加载的类是ClassLoaderTest,为什么还要加载java.lang.Object?

 

带着问题,断点跟踪一下:loadClass

执行MyClassLoader1   的defineClass(name, b, 0, b.length );方法时,其调用顺序如下:

defineClass(name, b, 0, b.length );
 2 
 3 ---> ClassLoader.class
 4  protected final Class defineClass(String name, byte[] b , int off, int len)
 5         throws ClassFormatError
 6     {
 7         return defineClass(name, b, off, len, null);
 8     }
 9 
10 -->
11 
12 protected final Class defineClass(String name, byte[] b, int off, int len,
13                                    ProtectionDomain protectionDomain)
14         throws ClassFormatError
15     {
16          return defineClassCond(name, b, off, len, protectionDomain, true);
17     }
18 
19 --->
20 
21 private final Class defineClassCond(String name,
22                                            byte[] b, int off, int len,
23                                            ProtectionDomain protectionDomain,
24                                            boolean verify)
25         throws ClassFormatError
26     {
27        protectionDomain = preDefineClass(name, protectionDomain);
28 
29        Class c = null;
30         String source = defineClassSourceLocation(protectionDomain);
31 
32         try {
33            c = defineClass1(name, b, off, len, protectionDomain, source,
34                              verify);
35        } catch (ClassFormatError cfe) {
36            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
37                                        source, verify);
38        }

defineClass1也就是提示程序出错的位置

看看defineClass1方法是什么?在ClassLoader中,定义如下:

 private native Class defineClass1(String name, byte[] b, int off, int len,

                                     ProtectionDomain pd, String source,

                                      boolean verify);

也就是说defineClass1是native方法,继续跟踪代码,就发现又调用到自定义的loadClass方法中,此时传入的参数则变成了java.lang.Object。

根据这些可以猜测,类的加载,会将其所有的父类都加载一遍,直到java.lang.Object。

为了验证,这个猜想,写出以下示例。

public class MyClassLoader1   extends ClassLoader{

        @Override

        public Class loadClass(String name) throws ClassNotFoundException{

               try{

                     System. out.println(name);

                     String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";

                     InputStream is = this.getClass().getResourceAsStream(fileName);

                      byte[] b = new byte[is.available()];

                     is.read(b);

                      return defineClass(name, b, 0, b.length );

              } catch(IOException e){

                      throw new ClassNotFoundException(name);

              }

       }

}

 

public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

              MyClassLoader1 myLoader = new MyClassLoader1();

              Object obj = myLoader.loadClass("com.csair.soc.SubSample" ).newInstance();

       }

SubSample有父类Sample。

输出结果:

com.csair.soc.SubSample

com.csair.soc.Sample

java.lang.Object

Exception in thread "main" java.lang.NullPointerException

       at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:13 )

       at java.lang.ClassLoader.defineClass1( Native Method )

       at java.lang.ClassLoader.defineClassCond( ClassLoader.java:631 )

       at java.lang.ClassLoader.defineClass( ClassLoader.java:615 )

       at java.lang.ClassLoader.defineClass( ClassLoader.java:465 )

       at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:15 )

       at com.csair.soc.ClassLoaderTest.main( ClassLoaderTest.java:7 )

测试结果和猜测的一样,ClassLoader在加载类的同时,会通过native方法defineClass1,将其所有的父类都加载。当ClassLoader加载父类时,由于loadClass方法被重写,defineClass1会调用自定义的classLoader方法加载父类,因此出现以上错误。过程如下:

Java自定义类加载器以及加载类及其父类的过程_第1张图片

为了解决这个问题,可以使用以下方式,修改loadClass,当找不到类文件时,使用父类的ClassLoader试试。

  @Override

        public Class loadClass(String name) throws ClassNotFoundException{

               try{

                     String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";

                     InputStream is = this.getClass().getResourceAsStream(fileName);

                      if(is == null ){

                            return super .loadClass(name);

                     }

                      byte[] b = new byte[is.available()];

                     is.read(b);

                      return defineClass(name, b, 0, b.length );

              } catch(IOException e){

                      throw new ClassNotFoundException(name);

              }

       }

但最好的办法是不重写loadClass方法,而是重写findClass方法,同样可以达到目的。

这点在ClassLoader的loadClass方法的注释中有提及

 * 

 Subclasses of ClassLoader are encouraged to override {@link

     * #findClass(String)}, rather than this method.  

 

重写findClass后,代码如下:

@Override

        public Class findClass(String name) throws ClassNotFoundException{

               try{

                     String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";

                     InputStream is = this.getClass().getResourceAsStream(fileName);

                      byte[] b = new byte[is.available()];

                     is.read(b);

                      return defineClass(name, b, 0, b.length );

              } catch(IOException e){

                      throw new ClassNotFoundException(name);

              }

       }

输出结果:

class com.csair.soc.ClassLoaderTest

com.csair.soc.MyClassLoader@bfc8e0

false

可以看到类的加载是使用自定义的类加载器,在判断obj instanceof com.csair.soc.ClassLoaderTest时,由于默认的com.csair.soc.ClassLoaderTest使用的是系统类加载器,因此输出为false。

当然,也只有在父类加载器找不到类文件的时候,才会调用子类的findClass方法去寻找类文件。

你可能感兴趣的:(JVM,JVM)