标签:
类加载器负责将class文件加载到内存中,并为之生成对应的java.lang.Class对象。对于任意一个类,都需要加载它的类加载器和这个类本身来确定该类在JVM中唯一性,也就是说,同一个class文件用两个不同的类加载器加载并创建两个java.lang.Class对象,即使两个对象来源自同一个class文件,它们也是不相等的,这里“相等”包括Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法,也包括使用instanceof关键字做对象所属关系判定情况。
验证代码如下:
public class ClassLoaderTest { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { // 自定义类加载器 ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } try { byte[] buff = new byte[is.available()]; is.read(buff); return defineClass(name, buff, 0, buff.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; // 使用自定义的类加载加载classLoaderTest.ClassLoaderTest类 Class clazz = myLoader.loadClass("classLoaderTest.ClassLoaderTest"); Object obj = clazz.newInstance(); /* 创建obj对象的类实例是由自定义类加载器加载的, classLoaderTest.ClassLoaderTest使用的类加载器是系统类加载器,所属关系并不一致 */ System.out.println(obj instanceof classLoaderTest.ClassLoaderTest); /* clazz类实例是由自定义类加载器加载的, classLoaderTest.ClassLoaderTest.class使用的类加载器是系统类加载器 */ System.out.println(clazz.getClassLoader()); System.out.println(classLoaderTest.ClassLoaderTest.class.getClassLoader()); System.out.println(clazz.equals(classLoaderTest.ClassLoaderTest.class)); } }
输出结果:
false classLoaderTest.ClassLoaderTest$1@537e7f88 sun.misc.Launcher$AppClassLoader@7d05e560 false
从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是虚拟机的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机,并且全部继承自java.lang.ClassLoader。细分来看,类加载器还可以分为如下几类:
Bootstrap ClassLoader被称为根类加载器,它负责加载Java的核心类。根类加载器并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。在Sun的JVM中,当执行java.exe命令时,使用-Xbootclasspath选择或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。
通过如下程序查看根类加载器加载的类的路径:public class LoaderTest { public static void main(String[] args) throws IOException { // 获取根类加载器所加载的全部URL数组 URL[] urls = Launcher.getBootstrapClassPath().getURLs(); // 遍历、输出根类加载器加载的全部URL System.out.println("*********根类加载器加载的全部URL*************"); for (URL url : urls) { System.out.println(url.toExternalForm()); } } }
*********根类加载器加载的全部URL************* file:/D:/Java/jdk1.7.0_80/jre/lib/resources.jar file:/D:/Java/jdk1.7.0_80/jre/lib/rt.jar file:/D:/Java/jdk1.7.0_80/jre/lib/sunrsasign.jar file:/D:/Java/jdk1.7.0_80/jre/lib/jsse.jar file:/D:/Java/jdk1.7.0_80/jre/lib/jce.jar file:/D:/Java/jdk1.7.0_80/jre/lib/charsets.jar file:/D:/Java/jdk1.7.0_80/jre/lib/jfr.jar file:/D:/Java/jdk1.7.0_80/jre/classes
Extension ClassLoader是扩展类加载器,它负责加载JRE的扩展目录中JAR包的类,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
Application ClassLoader是应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,可以通过ClassLoader.getSystemClassLoader()方法获取,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath)上指定的类库,开发者可以直接使用这个类加载器,如果应用程序没有自定义类加载器,一般情况下这个就是程序中默认的类加载器。
为了实现类加载的个性化定制,我们可以通过扩展java.lang.ClassLoader类来实现自定义类加载器,详细的实现随后描述。
我们的应用程序一般都是由这几种类加载器相互配合进行加载的,类加载器之间的关系如图:
类加载器之间的关系并非是类继承性质的父子关系,而是一种组合关系。
类加载器在加载一个类时会先把加载的请求委托给父加载器加载,如果父类加载器仍有父加载器(只有根加载器没有父加载器),则再次委托给其父加载器,最后所有的加载请求都会传送给根类加载器,只有当父类加载器中无法完成对委托的类的加载时,该类才会被子加载器加载。这样的加载方式显著的好处就是:在一个运行的JVM中,所有的类都不会被不同的类加载器,某个类在各个加载器环境中看到的都是同一个Class对象。
另外,当一个类加载器负责加载某个Class时,该Class所引用的其他Class也会被该类加载器载入,除非显式使用另外一个类加载器加载。
JVM的类加载有一种缓存机制,如果一个类已经被加载过,那么在程序中再次用到这个类时,会直接从缓存中获取该类,只有在缓存中不存在该类时,类加载器才会读取该类的二进制数据并创建该类的Class对象。这也很好地解释了为什么修改了Java代码之后需要重启JVM修改才能生效。不过,现在已经有技术能够做到修改Java代码后不重启JVM仍可生效。
在日常的程序开发中,出于某种需要,我们可能要自定义一些类加载器。通常推荐扩展ClassLoader类并重写findClass方法来实现自定义类加载器。
接下来我们自定义实现一个可以直接加载源代码的加载类,实现的原理很简单,把对源码的编译放在类加载器中执行:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; /** * author:xszhaobo * <p/> * date:2016/5/7 * <p/> * package_name:JVMTest.myclassloader * <p/> * project: _darksiderg */ public class CompileClassLoader extends ClassLoader { public static void main(String[] args) throws Exception { if (args.length < 1) { System.out.println("缺少目标类:"); System.out.println("java CompileClassLoader ClassName"); } // 第一个参数指定需要运行的类名 String progClass = args[0]; // 其他的一些参数 String[] progArgs = new String[args.length - 1]; System.arraycopy(args,1,progArgs,0,progArgs.length); // 指定类加载器 CompileClassLoader loader = new CompileClassLoader(); Class clazz = loader.loadClass(progClass); // main指方法名称 // new String[0]).getClass()表示main的形参类型对应Class Method main = clazz.getMethod("main",(new String[0]).getClass()); Object[] argsArray = {progArgs}; // 第一个参数表示需要调用的方法所属的对象,如果调用静态方法,那么可以设置为null // 第二个以及以后的参数表示该方法传入的参数 main.invoke(null,argsArray); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("加载类:" + name + ".java"); Class clazz = null; String fileStub = name.replaceAll("\\.", "/"); String javaFileName = fileStub + ".java"; String classFileName = fileStub + ".class"; File javaFile = new File(javaFileName); File classFile = new File(classFileName); // java源文件存在而class文件不存在,或者java源文件修改时间晚于class文件 if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { if (!compile(javaFileName) || !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundException:" + javaFileName); } } catch (IOException e) { e.printStackTrace(); } if (classFile.exists()) { try { byte[] raw = getBytes(classFileName); clazz = this.defineClass(name,raw,0,raw.length); } catch (IOException e) { e.printStackTrace(); } } } if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; } private boolean compile(String javaFile) throws IOException { System.out.println("CompileClassLoader编译" + javaFile + "..."); Process p = Runtime.getRuntime().exec("javac -encoding utf-8 " + javaFile); try { p.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } int ret = p.exitValue(); return ret == 0; } private byte[] getBytes(String fileName) throws IOException { File file = new File(fileName); long length = file.length(); byte[] bytes = new byte[(int) length]; // 使用Java 7的特性:具有资源管理的try语句 try (FileInputStream fis = new FileInputStream(file)) { int readLen = fis.read(bytes); if (readLen != length) { throw new IOException("无法读文件的全部内容:" + fileName); } return bytes; } // Java 7之前的版本使用这种资源管理方式 /*try { int readLen = fis.read(bytes); if (readLen != length) { throw new IOException("无法读文件的全部内容:" + fileName); } return bytes; } finally { fis.close(); }*/ } }
指令的含义
javac是编译命令;
-encoding utf-8指定编码是utf-8;
-Xlint:unchecked指由于CompileClassLoader.java使用了未经检查或不安全的操作,需要指定-Xlint:unchecked编译;
CompileClassLoader.java指需要编译的文件。
在相同的文件夹下面随便写一个测试主类:
/** * author:xszhaobo * <p/> * date:2016/5/9 * <p/> * package_name:JVMTest.myclassloader * <p/> * project: _darksiderg */ public class HelloTest { public static void main(String[] args) { System.out.println("参数 :" + args[0]); } }
CompileClassLoader编译HelloTest.java... 参数:这是一个参数可以看出这是此时先编译了HelloTest.java文件,然后运行该类中的main方法。
参数:这是一个参数此时的输出已经不包含“CompileClassLoader编译HelloTest.java...”这一句,说明这一次并没有重新编译源代码,该问题的原因尚未找到,如果你知道答案,不妨告诉我。
标签:
原文地址:http://blog.csdn.net/yuxxz/article/details/51357129