java中的类加载
Class实例
java在真正需要一个类时才由Java虚拟机JVM加载类,所谓真正需要是要通过类来构造对象或者用户自己指定要加载类。被夹在的类在java虚拟机JVM中都以一个Class实例存在。Class对象没有公开的构造器,Class对象有虚拟机JVM自动产生。也就是说,每一个类被加载,JVM虚拟机就自动为该类产生一个Class实例。
Class的信息是在编译时期就被加入至.class文中,这是Java支持运行时类型识别(RTTI)的一种方式。一个类在JVM中只有一个Class实例,每个类的实例都会记住自己是有那个Class实例所生成。
使用Class.forName()加载类
Class的静态方法有两个版本,一个版本只需指定类名,而另一个版本可以指定类名称,加载类时是否运行静态区,指定类加载器:
static Class forName(String name, boolean initialize, ClassLoader loader)
在使用第二个版本时,可以将initialize设置为false,这样加载类时就不会运行静态区。
下面是测试类的代码:
package onlyfun.caterpillar; public class TestClass2 { static { System.out.println("[运行静态区]"); } }
package onlyfun.caterpillar; public class ForNameDemoV1 { public static void main(String[] args) { try { System.out.println("载入TestClass2"); Class c = Class.forName("onlyfun.caterpillar.TestClass2"); //使用forName的第一个版本加载类 System.out.println("使用TestClass2声明参考名称"); TestClass2 test = null; System.out.println("使用TestClass2建立对象"); test = new TestClass2(); } catch (ClassNotFoundException e) { System.out.println("找不到指定的类"); } } }
运行结果:
从结果看,当使用forName方法第一个版本加载类时,静态区立即运行
下面是forName的第二个版本
package onlyfun.caterpillar; public class ForNameDemoV2 { public static void main(String[] args) { try { System.out.println("载入TestClass2"); //用forName的第二个版本加载类,initialize设置为false,不会运行静态区, 并指明了类加载器 Class c = Class.forName("onlyfun.caterpillar.TestClass2", false, Thread.currentThread().getContextClassLoader()); System.out.println("使用TestClass2声明参考变量"); TestClass2 test = null; System.out.println("使用TestClass2建立对象"); test = new TestClass2(); } catch (ClassNotFoundException e) { System.out.println("找不到指定的类"); } } }
运行结果:
从结果看,用forName方法加载TestClass2时并没有运行静态区
类加载器
当运行java程序的时候,运行程序会找到JRE的安装目录,然后寻找jvm.dll(默认在JRE目录下bin\client目录中), 接着JVM启动并初始化,产生Bootstrap Loader, Bootstrap Loader加载Extended Loader,并设置Bootstrap Loader为其parent,然后Extended Loader(也就是ExtClassLoader)加载System Loader(也就是AppClassLoader),并设置Extended Loader为其parent。
Bootstrap Loader用C编写,其他两个Loader为java语言编写
Bootstrap Loader会搜索系统参数sun.boot.class.path中指定的类,默认是JRE所在目录的classes下的.class文件,或者lib目录下的.jar文件中的类(如rt.jar)并加载。可以通过System.getProperty("sun.boot.class.path")语句显示sun.boot.class.path中指定的路径。
Extended Loader会搜索系统参数java.ext.dirs中指定的类,默认是JRE目录下的lib/ext/classes目录下的.class文件,或者lib/ext目录下的.jar文件(如 rt.jar)中的类并加载。可以通过System.getProperty("java.ext.dirs")语句来显示java.ext.dirs中指定的路径。
System Loader会搜索系统参数java.class.path中指定的类,也就是Classpath所指定的路径,默认是当前工作路径下的.class文件。可以使用System.getProperty("java.class.path")来显示java.class.path中指定的路径。在java运行时,也可以通过-cp来覆盖原来的Classpath设置:java -cp ./classes someClass.
每个类加载器都会先将加载任务交给其parent,如果parent找不到,再由正自己负责,如果还找不到,就抛出NoClassDefFoundError。
类加载器在java中都已java.lang.ClassLoader类型存在,每一个加载的类都有一个Class实例代表,而每个Class实例都会记住自己是哪一个类加载器加载的。可以有Class的getClassLoader方法取得该类的ClassLoader,耳从ClassLoader的getParent方法可以取到自己的parent。
取得ClassLoader的实例后,可以使用loadClass方法来加载类。这个方法不会运行静态区。
package onlyfun.caterpillar; public class ForNameDemoV3 { public static void main(String[] args) { try { System.out.println("使用类加载器加载类"); ClassLoader loader = ForNameDemoV3.class.getClassLoader();//获取类加载器 Class c = loader.loadClass("onlyfun.caterpillar.TestClass2"); System.out.println("使用TestClass2声明参考名称"); TestClass2 test = null; System.out.println("使用TestClass2建立对象"); test = new TestClass2(); } catch (ClassNotFoundException e) { System.out.println("找不到指定的类"); } } }
运行结果:
使用自己的ClassLoader
ExClassLoader和AppClassLoader都是java.net.URLClassLoader的子类,可以在使用java时用下列指令指定ExtClassLoader的所搜路径:
java -Djava.ext.dirs = c:\workspace\Yourclass
可以用-cp或者-classpath指定AppClassLoader的搜索路径:
java -cp c:\workspace\Yourclass
可以使用URLClassLoader来产生新的类加载器,它需要java.net.URL作其参数指定加载类时的搜索路径:
URL url = new URL("file:/d:/workspace/"); ClassLoader urlClassLoader = new URLClassLoader(new URL[] {url}); Class c = urlClassLoader.loadClass("Someclass");
新建的ClassLoader会将其parent设置为AppClassLoader
由同一个类加载器加载的类文件,会只有一份Class实例,如果同一个类文件有两个不同的ClassLoader载入,就会有两份不同的Class实例