一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。
http://blog.youkuaiyun.com/seelye/article/details/8266192
java类装载器
Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:
Bootstrap Loader - 负责加载系统类
|
- - ExtClassLoader - 负责加载扩展类
|
- - AppClassLoader - 负责加载应用类
加载类的过程
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备、解析 装载:查找和导入类或接口的二进制数据; 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的; 校验:检查导入类或接口的二进制数据的正确性; 准备:给类的静态变量分配并初始化存储空间; 解析:将符号引用转成直接引用;初始化:激活类的静态变量,初始化Java代码和静态Java代码块。
-----------这样就得到了一个类实例(java中类是Class类的实例),注意,不是该类的对象,是类本身。有点绕!
不同的加载器加载的.class相同,得到的类实例也不同(类名加上加载器名,用以标识)。
Java 的类加载器的工作原理
当执行 java ***.class 的时候, java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器―― Bootstrap Loader ,
Bootstrap Loader 是由 C++ 所撰写而成,这个 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader (Launcher$ExtClassLoader.class),并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。
然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader (Launcher$AppClassLoader.class),并设定其 (Launcher$AppClassLoader.class)Parent 为之前产生的 ExtClassLoader 实体。
这两个加载器(ext..和app..)都是以静态类的形式存在的。
这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。
下面的图形可以表示三者之间的(parent)关系:
BootstrapLoader <---(Extends)----AppClassLoader <---(Extends)----ExtClassLoader
这三个加载器就构成我们的 Java 类加载体系。他们分别从以下的路径寻找程序所需要的类:
BootstrapLoader : sun.boot.class.path --------如...\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
ExtClassLoader : java.ext.dirs --------------------如..\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\resources.jar; 以及该lib目录下的 rt.jar; sunrsasign.jar; jsse.jar; jce.jar; charsets.jar; ..\jre\classes(why我的目录下没有?难道可以将自己的*.class放在这,由ExtClassLoader加载?)
AppClassLoader: java.class.path ---------------如D:\Documents and Settings\yutian\Workspace\....(项目目录)
这三个系统参量可以通过 System.getProperty() 函数得到具体对应的路径。大家可以自己编程实现查看具体的路径。
JVM三级加载器,一般是一级级递归交给所调用的加载器的父类加载,父类无法加载(注意,各个加载器的寻找路径不同,因此,父加载器才会无法加载,子加载器才会加载自己搜索路径下的类)时,才会一级级下降交给子类加载。这是有JVM加载器本身实现的,不用你自己去递归。
当然如果你写自己的加载器加载自己定义的类,不去寻求父类加载,也是可以的。(对吗??)
手动加载类
我们书写的程序中普通类装载方式,有两种
1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中(即import+new)
2.显式装载, 通过class.forname()等方法,显式加载需要的类
import仅仅将包给导入,并没有加载包。
class.forName的话就是加载了包。读取包里面的内容到内存中。
http://blog.youkuaiyun.com/ling091/article/details/7631024
获得类实例的方法:
利用对象调用getClass()方法获取该对象的Class实例
使用Class的静态方法forName(),用类的名字获取一个Class实例
Class.forName
这是一个类方法,该方法除了加载类到JVM中,还会执行类的初始化,即类的静态初始化。
使用Class.forName最常用的形式是Class.forName(String qualifiedClassName),如写JDBC程序常用到的加载某个数据库Driver到jvm的代码:
Class.forName("com.mysql.jdbc.Driver");
ClassLoaderInstance.loadClass
这是一个实例方法,可以使用一个特定的ClassLoader来加载一个类。该方法不会执行类的初始化。
只由当该类第一次被使用时才会执行类的初始化,比如调用该类的一个静态方法或创建该类的一个对象。
加载Mysql Driver的例子中,我们本身并不需要创建MySqlDriver对象,只需要加载Driver到JVM,并执行类的初始化(可能执行注册自己到Driver Manger的代码)。所以可以使用forName方法,如果使用ClassLoader.loadClass则需要像下面这样:
ClassLoader cl = …;
cl.loadClass("com.mysql.jdbc.Driver").newInstance(); //创建对象(.newInstance();该实例是类本身,不是类的实例)仅仅为了执行类初始化,对象本身并不会被使用。
注意:如果要加载的类不在默认的查找位置时(如上例),就应该指定要使用的ClassLoader,否则会得到java.lang.ClassNotFoundException的错误。为了灵活性,你应该总是提供一个来参数指定ClassLoader而不是使用默认的ClassLoader,即使用带三个参数的forName或loadClass方法。关于不同的ClassLoader如何查找及加载类请参考ClassLoder一文。
编写自己的类加载器
http://book.51cto.com/art/200812/104168.htm
如果要编写自己的类加载器,只需要继承ClassLoader类,然后覆盖下面这个方法
findClass(String className) |
ClassLoader超类的loadClass方法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无法加载该类时,才调用findClass方法。
如果要实现该方法,必须做到以下几点:
1. 为来自本地文件系统或者其他来源的类加载其字节码。
2. 调用ClassLoader超类的defineClass方法,向虚拟机提供字节码。