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,并设定其parent为null,代表父载入器为Bootstrap。接着Bootstarp又载入Launcher中的AppClassLoader(第三个),Launcher¥AppClassLoader,并设定parent为之前的ExtClassLoader实例。 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:
sun.misc..defineClass(
arg0, byte[] arg1, int arg2, int arg3,
arg4,
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块被调用了两次,也就是说在一个虚拟机中,相同的类被载入了两次,
ClassLoaderTest被AppClassLoader(又称为SystemLoader,系统载入器)所载入,URL和URLCLassLoader,为null,表示由BootstrapLoader载入,它不是java写的,所以没有实例。两个TestAction,分别由不同的ClassLoader实例载入。
|