分享

ClassLoader(1)ClassLoader和Class

 碧海山城 2010-08-08

1.java源码和class文件Class

  java源码都被编译为class文件,jvm真正启动之后还是要找到对应的Class,靠的就是ClassLoader


2.启动以及三个ClassLoader

当我们通过命令java XXMain.class的时候,java.exe根据我们的设置找到了JRE,接着找到JRE钟的jvm.dll,真正的java虚拟机,最后载入这个动态函数库,启动Java虚拟机,虚拟机一启动,首先做一些初始化的动作,获得系统参数等,初始化完成后,就会产生第一个ClassLoader,即Bootstrap Loader,这个Loader除了初始化动作之外,最重要的就是载入定义在sun.misc命名空间下的Launcher,java中的ExtClassLoader,因为是内部类,所以编译后变成(第2个)Launcher$ExtClassLoader,并设定其parentnull,代表父载入器为Bootstrap。接着Bootstarp又载入Launcher中的AppClassLoader(第三个)LauncherAppClassLoader,并设定parent为之前的ExtClassLoader实例

关于三者的详细详细关系以及委托加载机制,请看 ClassLoader(2)Boot、App、Ext、线程加载器
 

3.获取Class的ClassLoader

Class类是一个样板,实例就是样板产生的实体,在Java中,每个类都继承了Object,Object里的getClass方法,就是用来取得某个特定实体所属类的参考,它指向一个Class.class的实例,Class本身构造函数是私有的,所以不能自己产生实例,它是在类第一次载入内存的时候建立的,每个实例内部都会记录这些个Class的位置

 

Class的实例是在类第一次载入内存的时候建立的

(关于Class实例的内存分布,可以参考我的GC文章)


基本上,我们可以把每个Class的实体,当做是某个类,在内存中的代理人,每次我们需要查询某个类的资料,比如field、method,就可以使用它,事实上,Java的反射机制,就大量的利用Class类,它的大部分方法都是本地方法通过Class的newInstance方法,可以得到一个实例。


Java中,每个Class都是由一个ClassLoader实体来载入的,每个Class实体中,都记录它的ClassLoader的实体,(如果是null,并不是代表不是没有ClassLoader,而是代表他是由bootstrap loader,或者叫root loader载入的,因为他们不是java写的,所以没有实体)。

 

只要取得Class的实例就可以通过getClassLoader取得对应的ClassLoader,

ClassLoader cl=Vector.class.getClassLoader();

       class c=cl.loadClass("Hello");

c.newInstance();

这种方式也不会初始化静态代码块,和Class.forName第二个参数为false几乎是一样的。


4.几种载入Class的方法

虽然我们知道ClassLoader用来载入Class,但是我们调用Class.forName()的时候以及new的时候是怎么载入的?


4.1 new Object()

class A(){

   public A(){

     new B();

  }

}

当A里面new B的时候发生了什么?Class A一定是先载入了,不管是AppClassLoader还是自定义的UrlClassLoader,那么在A new B的时候,会按照以下顺序来确定B使用的ClassLoader:(参看 jvm spc5.3

1.The Java virtual machine uses one of three procedures to create class or interface C denoted by N:

  • 1.1If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:

    • 1.1.1If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).

    • 1.1.2If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).

  • 1.2Otherwise N denotes an array class. An array class is created directly by the Java virtual machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.

如果B不是数组,,那么采用A的加载器加载(Boot或者用户自定义加载器)

如果B是数组,那活在虚拟机直接创建代表数组的class,而不是通过classloader载入什么的,当然从使用角度来看,B还是由A的加载器加载的


    4.2 Class.forName()

其实,在使用JDBC的时候,就是使用Class.forName来动态装入类别的,Class有两个forName方法:


Public static Class forName(String className

Public static Class forName(String name,boolean initializa,ClassLoader loader)


如果没有传入ClassLoader,那么谁使用ClassLoader.getCallerClassloader,我的理解其实就是调用它的当前类加载器


最终,他们都是链接到了原生方法: (所以他使用的ClassLoader还是比较明确的,可以自定义也可以使用当前类加载器)

    private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;


一种说法,静态初始化块在类第一次载入的时候才会被调用仅一次,可是实际上当我们第二个参数为false的时候,只会在第一次载入类,但是不会调用静态初始化块,而是等到第一次实例化的时候,静态初始化才会被调用。


    4.3 ClassLoader.loadClass()

         所有的loader都是ClassLoader的子类,他的loadClsss方法就可以用来真正找到类,他的大致流程如下:


 

   4.4 ClassLoader.defineClass

         有些时候并不是知道了ClassName,而是得到了一个byte数组(一般是网络传输过来的情况,取代了从磁盘读取字节流的形式),这时候就可以调用defineClass方法直接得到Class


  4.5 unsafe.defineClass

api:Class sun.misc.Unsafe.defineClass(String arg0, byte[] arg1, int arg2, int arg3, ClassLoader arg4, ProtectionDomain arg5)
使用: unsafe.defineClass(className, btraceCode, 0, btraceCode.length, classLoader, null);

这种方式可以得到Class,而且还可以指定classloader

4.6 ObjectInputStream.readObject()

在一些序列化传输中,会直接调用readObject来读取对象,但是对象序列化本身只带一些实例和类描述信息,还是需要在接收端有对应的Class,序列化的格式参考:java默认序列化格式

序列化信息中包含了是哪个class名,

 private ObjectStreamClass readNonProxyDesc(boolean unshared) 

throws IOException {

......

     .....

ObjectStreamClass readDesc = null;

try {

    readDesc = readClassDescriptor();

catch (ClassNotFoundException ex) {

    throw (IOException) new InvalidClassException(

"failed to read class descriptor").initCause(ex);

}

Class cl = null;

ClassNotFoundException resolveEx = null;

bin.setBlockDataMode(true);

try {

    if ((cl = resolveClass(readDesc)) == null) {

resolveEx = new ClassNotFoundException("null class");

    }

catch (ClassNotFoundException ex) {

    resolveEx = ex;

}

..........

}

可以看到从序列化信息中,读取到的是Class的一些描述信息,ObjectStreamClass,他包含必须的自我发现的一些必要信息:http://docs.oracle.com/javase/6/docs/api/java/io/ObjectStreamClass.html 

可以覆盖次方法,根据className,从远程或者其他地方获取类信息。

5.自定义ClassLoader


URLClassLoader的实例来帮我们载入所需要的类,载入之前,告诉URLClassLoader去哪个地方寻找类,URL可以指向网络上的任何位置


TODO


6.被载入多次的类


        参考下面的代码:


URL url=new URL("file:/C:/jar1.jar");

URLClassLoader ucl=new URLClassLoader(new URL[]{url});

Class c=ucl.loadClass("bhsc.hello.TestAction");

ActionInterface a1=(ActionInterface)c.newInstance();

a1.action();

//,Thread.currentThread().getContextClassLoader()

URL url1=new URL("file:/C:/jar1.jar");

URLClassLoader u2=new URLClassLoader(new URL[]{url1});

Class c1=u2.loadClass("bhsc.hello.TestAction");

c.newInstance();

ActionInterface a2=(ActionInterface)c1.newInstance();

a2.action();

System.out.println(ClassLoaderTest.class.getClassLoader());

System.out.println(ActionInterface.class.getClassLoader());

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

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

System.out.println(c.getClassLoader());

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

System.out.println("==============================");

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

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

System.out.println(c1.getClassLoader());

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

结果:

static init

TestAction

static init

TestAction

sun.misc.Launcher$AppClassLoader@19821f

sun.misc.Launcher$AppClassLoader@19821f

null

null

java.net.URLClassLoader@c17164

java.net.URLClassLoader@c17164

==============================

null

null

java.net.URLClassLoader@ca0b6

java.net.URLClassLoader@ca0b6

ActionInterface是一个接口,TestAction是实现类,项目不能引用到TestAction(如果能够引用到TestAction的话又是另一种结果,待会再说)。


可以从结果看到,类的static块被调用了两次,也就是说在一个虚拟机中,相同的类被载入了两次,

ClassLoaderTestAppClassLoader(又称为SystemLoader,系统载入器)所载入,URLURLCLassLoader,为null,表示由BootstrapLoader载入,它不是java写的,所以没有实例。两个TestAction,分别由不同的ClassLoader实例载入

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多