Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
Java之类加载机制
类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行。 研究类加载机制的第二个目的是让程序能动态的控制类加载,比如热部署等,提高程序的灵活性和适应性。 一、简单过程 Java程序运行的场所是内存,当在命令行下执行: java HelloWorld 命令的时候,JVM会将HelloWorld.class加载到内存中,并形成一个Class的对象HelloWorld.class。 其中的过程就是类加载过程: 1、寻找jre目录,寻找jvm.dll,并初始化JVM; 2、产生一个Bootstrap Loader(启动类加载器); 3、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。 4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。 5、最后由AppClass Loader加载HelloWorld类。 以上就是类加载的最一般的过程。 二、类加载器各自搜索的目录
为了弄清楚这个问题,首先还要看看System类的API doc文档。
1、Bootstrap Loader(启动类加载器):加载System.getProperty("sun.boot.class.path")所指定的路径或jar。 2、Extended Loader(标准扩展类加载器ExtClassLoader):加载System.getProperty("java.ext.dirs")所指定的路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:/projects/testproj/classes HelloWorld 3、AppClass Loader(系统类加载器AppClassLoader):加载System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld ExtClassLoader和AppClassLoader在JVM启动后,会在JVM中保存一份,并且在程序运行中无法改变其搜索路径。如果想在运行时从其他搜索路径加载类,就要产生新的类加载器。 三、类加载器的特点 1、运行一个程序时,总是由AppClass Loader(系统类加载器)开始加载指定的类。 2、在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。3、Bootstrap Loader(启动类加载器)是最顶级的类加载器了,其父加载器为null. 四、类加载器的获取 很容易,看下面例子 public class HelloWorld {
public static void main(String[] args) {
HelloWorld hello = new HelloWorld();
Class c = hello.getClass();
ClassLoader loader = c.getClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
} 打印结果: sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null
Process finished with exit code 0 从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(启动类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null。 五、类的加载 类加载有三种方式: 1、命令行启动应用时候由JVM初始化加载 2、通过Class.forName()方法动态加载 3、通过ClassLoader.loadClass()方法动态加载 三种方式区别比较大,看个例子就明白了: public class HelloWorld {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld. class.getClassLoader();
System.out.println(loader);
//使用ClassLoader.loadClass()来加载类,不会执行初始化块
loader.loadClass( "Test2");
//使用Class.forName()来加载类,默认会执行初始化块
// Class.forName("Test2");
//使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块
// Class.forName("Test2", false, loader);
}
} public class Test2 {
static {
System.out.println( "静态初始化块执行了!");
}
} 分别切换加载方式,会有不同的输出结果。 六、自定义ClassLoader 为了说明问题,先看例子: package test;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 自定义ClassLoader
*
* @author leizhimin 2009-7-29 22:05:48
*/
public class MyClassLoader {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
URL url = new URL( "file:/E://projects//testScanner//out//production//testScanner");
ClassLoader myloader = new URLClassLoader( new URL[]{url});
Class c = myloader.loadClass( "test.Test3");
System.out.println( "----------");
Test3 t3 = (Test3) c.newInstance();
}
} public class Test3 {
static {
System.out.println( "Test3的静态初始化块执行了!");
}
} 运行后: ----------
Test3的静态初始化块执行了!
Process finished with exit code 0 可以看出自定义了ClassLoader myloader = new URLClassLoader(new URL[]{url});已经成功将类Test3加载到内存了,并通过默认构造方法构造了对象Test3 t3 = (Test3) c.newInstance(); 有关ClassLoader还有很重要一点: 同一个ClassLoader加载的类文件,只有一个Class实例。但是,如果同一个类文件被不同的ClassLoader载入,则会有两份不同的ClassLoader实例(前提是着两个类加载器不能用相同的父类加载器)。
在java.lang包里有个ClassLoader类,ClassLoader 的基本目标是对类的请求提供服务,按需动态装载类和资
源,只有当一个类要使用(使用new 关键字来实例化一个类)的时候,类加载器才会加载这个类并初始化。
一个Java应用程序可以使用不同类型的类加载器。例如Web Application Server中,Servlet的加载使用开发
商自定义的类加载器, java.lang.String在使用JVM系统加载器,Bootstrap Class Loader,开发商定义的其他类
则由AppClassLoader加载。在JVM里由类名和类加载器区别不同的Java类型。因此,JVM允许我们使用不同
的加载器加载相同namespace的java类,而实际上这些相同namespace的java类可以是完全不同的类。这种
机制可以保证JDK自带的java.lang.String是唯一的。
2. 加载类的两种方式:
(1) 隐式方式
使用new关键字让类加载器按需求载入所需的类
(2) 显式方式
由 java.lang.Class的forName()方法加载
public static Class forName(String className)
public static Class forName(String className, boolean initialize,ClassLoader loader)
参数说明:
className - 所需类的完全限定名
initialize - 是否必须初始化类(静态代码块的初始化)
loader - 用于加载类的类加载器
调用只有一个参数的forName()方法等效于 Class.forName(className, true, loader)。
这两个方法,最后都要连接到原生方法forName0(),其定义如下:
private static native Class forName0(String name, boolean initialize,ClassLoader loader)
throws ClassNotFoundException;
只有一个参数的forName()方法,最后调用的是:
forName0(className, true, ClassLoader.getCallerClassLoader());
而三个参数的forName(),最后调用的是:
forName0(name, initialize, loader);
所以,不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含
了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数
为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块
才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。
直接使用类加载器
获得对象所属的类 : getClass()方法
获得该类的类加载器 : getClassLoader()方法
3.执行java XXX.class的过程
找到JRE——》找到jvm.dll——》启动JVM并进行初始化——》产生Bootstrap Loader——》
载入ExtClassLoader——》载入AppClassLoader——》执行java XXX.class
ClassLoader是用来处理类加载的类,它管理着具体类的运行时上下文。
1.ClassLoader存在的模块意义:
1)从java的package定义出发:
classloader是通过分层的关联方式来管理运行中使用的类,不同的classloader中管理的类是不相同的,或者即便两个类毫无二致(除了路径)也是不同的两个类,在进行强制转换时也会抛出ClassCastException。所以,通过classloader的限制,我们可以建立不同的package路径以区别不同的类(注意这里的“不同”是指,命名和实现完全一致,但是有不同的包路径。)。那么也是因为有特定的classloader,我们可以实现具体模块的加载,而不影响jvm中其他类,即发生类加载的冲突。
2)但是,如果两个在不同路径下的类(我们假定,这两个类定义中,不存在package声明,完全一样的两个类),经过不同的classloader加载,这两个类在jvm中产生的实例可以相互转换吗?
答案是否定的。即便这两个类除了存在位置不同之外,都完全一样。经由不同classloader加载的两个类依然是不同的两个对象。通过Class.newInstance()或者Class.getConstructor().newInstance()产生的对象是完全不同的实例。
以上两种情况,package可以使得我们的软件架构清晰,但那不是最终作用,如果跟classloader结合起来理解,效果更好。
2.ClassLoader的类加载机制:
ClassLoader作为java的一个默认抽象类,给我们带来了极大的方便,如果我们要自己实现相应的类加载算法的话。
每个类都有一个对应的class与之绑定,并且可以通过MyClass.class方式来获取这个Class对象。通过Class对象,我们就能获取加载这个类的classloader。但是,我们现在要研究的是,一个类,是如何通过classloader加载到jvm中的。
其中有几个关键方法,值得我们了解一番:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException;
我们可以假设一个实例在建立时,例如通过new方式,是经由如此步骤实现:ClassLoader.loadClass("classname",false).newInstance()。
接下来需要考虑的是loadClass方法为我们做了哪些工作?如何跟对应的.class文件结合,如何将对应的文件变成我们的Class对象,如何获得我们需要的类?
在ClassLoader类中,已经有了loadClass默认实现。我们结合源代码说明一下:
01 |
protected synchronized Class<?>
loadClass(String name, boolean resolve) |
02 |
03 |
throws ClassNotFoundException |
04 |
05 |
{ |
06 |
07 |
//
首先检查,jvm中是否已经加载了对应名称的类,findLoadedClass(String )方法实际上是findLoadedClass0方法的wrapped方法,做了检查类名的工 |
08 |
09 |
//作,而findLoadedClass0则是一个native方法,通过底层来查看jvm中的对象。 |
10 |
11 |
Class
c = findLoadedClass(name); |
|