疯狂JAVA讲义---第十八章:类加载
2009-02-06 22:28
246 查看
与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。
JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由Bootstrap ClassLoader加载;其中Extension ClassLoader负责加载扩展的Java class(例如所有javax.*开头的类和存放在JRE的ext目录下的类),Application ClassLoader负责加载应用程序自身的类。
什么时候JVM会使用ClassLoader加载一个类呢?当你使用java去执行一个类,JVM使用Application ClassLoader加载这个类;然后如果类A引用了类B,不管是直接引用还是用Class.forName()引用,JVM就会找到加载类A的ClassLoader,并用这个ClassLoader来加载类B。
似乎JVM自身的ClassLoader已经足够了,为什么我们还需要创建自己的ClassLoader呢?
因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写你自己的ClassLoader,你可以做到:
1)在执行非置信代码之前,自动验证数字签名
2)动态地创建符合用户特定需要的定制化构建类
3)从特定的场所取得java class,例如数据库中
4) 等等
下面举个例子从.java文件直接加载,eg
当一个java class被加载到JVM之后,它有没有可能被卸载呢?我们知道Win32有FreeLibrary()函数,Posix有dlclose()函数可以被调用来卸载指定的动态连接库,但是Java并没有提供一个UnloadClass()的方法来卸载指定的类。
在Java中,java class的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。既然是一种优化方法,那么就完全是JVM自行决定如何实现,对Java开发人员来说是完全透明的。
在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interface may be unloaded if and only if its class loader is unreachable. Classes loaded by the bootstrap loader may not be unloaded."
事实上我们关心的不是如何卸载类的,我们关心的是如何更新已经被加载了的类从而更新应用的功能。JSP则是一个非常典型的例子,如果一个JSP文件被更改了,应用服务器则需要把更改后的JSP重新编译,然后加载新生成的类来响应后继的请求。
其实一个已经加载的类是无法被更新的,如果你试图用同一个ClassLoader再次加载同一个类,就会得到异常(java.lang.LinkageError: duplicate class definition),我们只能够重新创建一个新的ClassLoader实例来再次加载新类。至于原来已经加载的类,开发人员不必去管它,因为它可能还有实例正在被使用,只要相关的实例都被内存回收了,那么JVM就会在适当的时候把不会再使用的类卸载。
JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由Bootstrap ClassLoader加载;其中Extension ClassLoader负责加载扩展的Java class(例如所有javax.*开头的类和存放在JRE的ext目录下的类),Application ClassLoader负责加载应用程序自身的类。
什么时候JVM会使用ClassLoader加载一个类呢?当你使用java去执行一个类,JVM使用Application ClassLoader加载这个类;然后如果类A引用了类B,不管是直接引用还是用Class.forName()引用,JVM就会找到加载类A的ClassLoader,并用这个ClassLoader来加载类B。
似乎JVM自身的ClassLoader已经足够了,为什么我们还需要创建自己的ClassLoader呢?
因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写你自己的ClassLoader,你可以做到:
1)在执行非置信代码之前,自动验证数字签名
2)动态地创建符合用户特定需要的定制化构建类
3)从特定的场所取得java class,例如数据库中
4) 等等
下面举个例子从.java文件直接加载,eg
public class CompileClassLoader extends ClassLoader { //读取一个文件的内容 private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int)len]; FileInputStream fin = new FileInputStream(file); //一次读取class文件的全部二进制数据 int r = fin.read(raw); if(r != len) throw new IOException("无法读取全部文件:" + r + " != " + len); fin.close(); return raw; } //定义编译指定Java文件的方法 private boolean compile(String javaFile) throws IOException { System.out.println("CompileClassLoader:正在编译 " + javaFile + "..."); //调用系统的javac命令 Process p = Runtime.getRuntime(). exec("javac " + javaFile); try { //其他线程都等待这个线程完成 p.waitFor(); } catch(InterruptedException ie) { System.out.println(ie); } //获取javac线程的退出值 int ret = p.exitValue(); //返回编译是否成功 return ret == 0; } //重写ClassLoader的findClass方法 protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; //将包路径中的点(.)替换成斜线(/)。 String fileStub = name.replace("." , "/"); 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 { //如果编译失败,或者该Class文件不存在 if(!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException( "ClassNotFoundExcetpion:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } //如果class文件存在,系统负责将该文件转换成Class对象 if (classFile.exists()) { try { //将class文件的二进制数据读入数组 byte[] raw = getBytes(classFilename); //调用ClassLoader的defineClass方法将二进制数据转换成Class对象 clazz = defineClass(name,raw,0,raw.length); } catch(IOException ie) { ie.printStackTrace(); } } //如果clazz为null,表明加载失败,则抛出异常 if(clazz == null) { throw new ClassNotFoundException(name); } return clazz; } //定义一个主方法 public static void main(String[] args) throws Exception { //如果运行该程序时没有参数,即没有目标类 if (args.length < 1) { System.out.println("缺少运行的目标类,请按如下格式运行Java源文件:"); 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 ccl = new CompileClassLoader(); //加载需要运行的类 Class<?> clazz = ccl.loadClass(progClass); //获取需要运行的类的主方法 Method main = clazz.getMethod("main" , (new String[0]).getClass()); Object argsArray[] = {progArgs}; main.invoke(null,argsArray); } }
当一个java class被加载到JVM之后,它有没有可能被卸载呢?我们知道Win32有FreeLibrary()函数,Posix有dlclose()函数可以被调用来卸载指定的动态连接库,但是Java并没有提供一个UnloadClass()的方法来卸载指定的类。
在Java中,java class的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。既然是一种优化方法,那么就完全是JVM自行决定如何实现,对Java开发人员来说是完全透明的。
在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interface may be unloaded if and only if its class loader is unreachable. Classes loaded by the bootstrap loader may not be unloaded."
事实上我们关心的不是如何卸载类的,我们关心的是如何更新已经被加载了的类从而更新应用的功能。JSP则是一个非常典型的例子,如果一个JSP文件被更改了,应用服务器则需要把更改后的JSP重新编译,然后加载新生成的类来响应后继的请求。
其实一个已经加载的类是无法被更新的,如果你试图用同一个ClassLoader再次加载同一个类,就会得到异常(java.lang.LinkageError: duplicate class definition),我们只能够重新创建一个新的ClassLoader实例来再次加载新类。至于原来已经加载的类,开发人员不必去管它,因为它可能还有实例正在被使用,只要相关的实例都被内存回收了,那么JVM就会在适当的时候把不会再使用的类卸载。
相关文章推荐
- 疯狂java讲义之类加载与反射
- [疯狂Java讲义精粹] 第十三章|类加载机制与反射
- [疯狂Java讲义精粹] 第四章|面向对象(上)
- 疯狂java讲义笔记 2.3.7
- 疯狂Java讲义
- 疯狂JAVA讲义---第十五章:输入输出(上)流的处理和文件
- 流程控制与数组——Java疯狂讲义
- 疯狂JAVA讲义---第九章(下):国际化
- [疯狂Java讲义精粹] 第九章|Annotation(注释)
- 疯狂Java讲义中的习题,设计一个控制台的梭哈游戏
- 疯狂JAVA讲义---第十六章:多线程(下)
- 【读书笔记】疯狂java讲义学习(二)—— 数据类型和运算符
- 控制循环语句(1)—[疯狂java讲义]LkxD
- 34、疯狂java讲义第三版
- 疯狂JAVA讲义---第三章(下):运算符
- [疯狂Java讲义精粹] 第五章|面向对象(下)
- 疯狂JAVA讲义笔记--第一章
- 运行失败的 疯狂java讲义示例 使用Annotation示例
- 疯狂java讲义 第三章 数据类型和运算符
- 疯狂java讲义之多线程