您的位置:首页 > 编程语言 > Java开发

java的类加载机制

2007-10-11 09:29 281 查看
王森写《Java深度歷險》第二章《深入類別載入器》一文对Java类加载机制给出了很好的解释,读了以后有个啥印象呢?

预先加载还是按需加载?

什么时候JVM加载程序需要的类呢?

两种情况:

在系统启动的时候全部加载进来 or 当程序调用类之前加载

SUN的JVM(1.4为例)使用了上面的两种调用方式。

举个例子:

public class Main {
public static void main(String[] args) {
new ClassA().MethodA();
}
} public class ClassA {
public void MethodA(){
System.out.println("Use ClassA");
}
}

java -verbose:class Main

用JDK5.0编译的结果

[Opened D:\Java\jdk1.5\jre\lib\rt.jar] (开始加载java基础类所在的包,可以看到JDK1.5就加载了 rt.jar jsse.jar jce.jar charsets.jar 几个jar包)
[Opened D:\Java\jdk1.5\jre\lib\jsse.jar]
[Opened D:\Java\jdk1.5\jre\lib\jce.jar]
[Opened D:\Java\jdk1.5\jre\lib\charsets.jar]
[Loaded java.lang.Object from D:\Java\jdk1.5\jre\lib\rt.jar] (开始加载jar包里面的类)

.

.

.
[Loaded java.security.cert.Certificate from D:\Java\jdk1.5\jre\lib\rt.jar]
[Loaded com.zte.classLoaderTest.Main from file:/D:/Eclipse/workspace3.2.2/ClassLoaderTest/bin/] (开始加载程序入口类Main)
[Loaded com.zte.classLoaderTest.ClassA from file:/D:/Eclipse/workspace3.2.2/ClassLoaderTest/bin/] (加载类ClassA)
Use ClassA
[Loaded java.lang.Shutdown from D:\Java\jdk1.5\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from D:\Java\jdk1.5\jre\lib\rt.jar] (看看SUN的注释 governing the virtual-machine shutdown sequence. 大家应该明白了吧?)

用JDK6.0编译的结果

[Loaded java.lang.Object from shared objects file] (看看JDK6.0是不是有点不一样啊?没有加载jar包的一步了,据说JDK6.0的JVM速度增加了,猜测是为了速度优化了)
[Loaded java.io.Serializable from shared objects file]

.

.

.

.
[Loaded java.security.Principal from shared objects file]
[Loaded java.security.cert.Certificate from shared objects file]
[Loaded com.zte.classLoaderTest.Main from file:/D:/Eclipse/workspace3.2.2/ClassLoaderTest/bin/] (同上)
[Loaded com.zte.classLoaderTest.ClassA from file:/D:/Eclipse/workspace3.2.2/ClassLoaderTest/bin/] (同上)
Use ClassA
[Loaded java.util.AbstractList$Itr from shared objects file] (从这里开始有点不一样了,这是在干什么?JDK6.0的shutdown吧!呵呵 )
[Loaded java.util.IdentityHashMap$KeySet from shared objects file]
[Loaded java.util.IdentityHashMap$IdentityHashMapIterator from shared objects file]
[Loaded java.util.IdentityHashMap$KeyIterator from shared objects file]
[Loaded java.io.DeleteOnExitHook from shared objects file]
[Loaded java.util.LinkedHashSet from shared objects file]
[Loaded java.util.HashMap$KeySet from shared objects file]
[Loaded java.util.LinkedHashMap$LinkedHashIterator from shared objects file]
[Loaded java.util.LinkedHashMap$KeyIterator from shared objects file]

从上面的例子可以很清除的看到在JVM刚启动的时候是使用预先加载(pre-loading)的机制将java基础类的jar包都加载进来。当基础类都加载结束以后,开始加载static main 所在的类。调用static main方法,方法中使用到了类ClassA 所以先加载ClassA,然后调用ClassA的方法。也就是说,当JVM执行完初始操作(加载基础类)以后,对待客户程序使用按需加载(load-on-demand)的方式,当程序需要的时候JVM执行加载操作。

Java程序的动态性

Java程序是具有动态性的,平时我们使用到Java的动态性比较少,new object()的时候我们没有意识到,但是Java的动态性开始启动了.......

Java有两种方式来达到动态性: 显示的隐式的

两种方式在底层实现上来说都使用的同样的机制,差异在Java程序设计师使用的代码不同。

隐式的(implicit)

当你的java代码中出现new的时候,类的动态加载机制就开始了。

显示的(explicit)

由于隐式的类加载没有弹性,所以SUN提供了显示的方式来动态加载类。

显示加载的两个方法:

Class的forName方法 & ClassLoader的loadClass方法

还是让我们看一个例子吧:

显示动态加载的例子

public interface Assembly {
public void run();
} public class Excel implements Assembly {
public void run() {
System.out.println("Excel is run");
}
} public class Word implements Assembly {
public void run() {
System.out.println("word is run");
} } public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName(args[0]);
Object o = c.newInstance();
Assembly a = (Assembly) o;
a.run();
}
}

有了代码就不多说了........

从上面的代码可以看到:

通过Class.forName(String s) 来获取类的Class类型 通过Class的newInstance()来加载类,获取实例。

还有一个方法Class forName(String s, boolean flag, ClassLoader classloader) s 表示需要加载的类的名称

flag表示加载类的时候是否先初始化类的静态区 classloader表示需要用到的类加载器

默认的一个参数的newInstance的flag是true,就是“载入类别+呼叫静态初始化块”

如果使用这种方法的话,需要有一个类加载器 由于ClassLoader.getCallerClassLoade()是私有的方法所以不能直接获取类加载器。所以我们使用的时候可以随便找一个类然后获取它的类加载器。

Test test = new Test();

ClassLoader c1 = test.getClass().getClassLoader();

或者

Class b = Test.Class;

ClassLoader c1 = b.getClassLoader();

这样就可以获取ClassLoader了。

然后使用Class.forName(String s).newInstance() 还是ClassLoader.loadClass()就自便了。

ClassLoader怎么获取是有一些说道的:

要想获取ClassLoader需要先有一个Class

在Java中每个类别的老祖宗都是Object,而Object有一个getClass的方法可以得到一个Class类别(Class.class)的实体。但这个方法是私有的,别想动。那么这个Class类别实体从何而来,是在.class载入JVM的时候生成的,以后我们再想得到一个类的实例的时候就需要通过这个Class.class代理来与JVM里面的.class来通讯了。

个人胡思乱想: Object里面的getClass肯定是交给JVM加载类的时候使用的;使用Class.class的时候如果该Class已经加载则OK,如果没有加载那么JVM会先加载它。

这个ClassLoader到底是何物,还不是很清楚,还是继续看下去吧!

自定义类加载器来加载类

这里我们使用到了Java的java.net.URLClassLoader

import java.net.URL;
import java.net.URLClassLoader; public class Main {
public static void main(String[] args) throws Exception {
// 使用默认的newInstance方法
// Class c = Class.forName(args[0]);
// Object o = c.newInstance(); // 使用
// ClassLoader loader = Word.class.getClassLoader();
// Class c = loader.loadClass(args[0]);
// Object o = c.newInstance(); // 自定义URLClassLoader
URL url = new URL("file:/D:/Eclipse/workspace3.2.2/ClassLoaderTest/com");
URLClassLoader loader = new URLClassLoader(new URL[] { url });
Class c = loader.loadClass(args[0]);
Object o = c.newInstance(); Assembly a = (Assembly) o;
a.run(); URL url2 = new URL("file:/D:/Eclipse/workspace3.2.2/ClassLoaderTest/com");
URLClassLoader loader2 = new URLClassLoader(new URL[] { url2 });
Class c2 = loader2.loadClass(args[0]);
Object o2 = c2.newInstance();
Assembly a2 = (Assembly) o2;
a2.run(); }
}

上面的例子按照王森所说的应该是出现同一个类别加载两次的情况,但我屡试不行。暂且记下吧!

按王森所说一个类只能被同一个ClassLoader加载一次,但是可以被不同的ClassLoader同时加载。

JVM中应用类加载器的结构

编译后的.class是如何启动的呢?下面说一下这个过程:

当我们在命令行输入 java XX(.class)的时候,java.exe根据一定的规则找到JRE(规则见下文)。接着找到JRE之中的JVM.DLL(真正的java虚拟机),最后载入这个动态链接库启动java虚拟机。

虚拟机一启动先做一些初始化的动作,比如获取系统参数等。之后就产生第一个类加载器,即所谓的Bootstrap Loader。Bootstrap Loader是由C++编写的,这个Loader进行了一些初始化操作以后,最重要的是载入定义在sun.misc命名空间下的Launcher.java之中的ExtClassLoader(因为是inner class ,所以编译之后变成Launcher$ExtClassLoader.class),并设定parent为null,代表其父加载器为Bootsrap Loader。然后,Bootstrap Loader再载入Launcher.java之中的AppClassLoader(同理为Launcher$AppClassLoader.class)并设定parent为ExtClassLoader

如上的过程用代码测试一下:

import java.net.URL;
import java.net.URLClassLoader; public class Main {
public static void main(String[] args) throws Exception {
ClassLoader loader = Main.class.getClassLoader();
System.out.println(loader);
ClassLoader loaderParent = loader.getParent();
System.out.println(loaderParent);
ClassLoader loaderParentParent = loaderParent.getParent();
System.out.println(loaderParentParent); }
} 结果为: sun.misc.Launcher$AppClassLoader@131f71a
sun.misc.Launcher$ExtClassLoader@15601ea
null

从上面的例子我们能够很清除的看出ClassLoader的层次关系。

【说明】Bootstrap Loader为null,是因为它是由C++写的没法表示

此外,大家需要注意的是AppClassLoader和ExtClassLoader都是URLClassLoader的子类别。

AppClassLoader搜索的路径是由java.class.path取出的路径决定的。

java.class.path值的设定是由-cp 或-classpath或path环境变量决定的。

System.out.println(System.getProperty("java.class.path"));

结果为:

D:\Eclipse\workspace3.2.2\ClassLoaderTest\bin

ExtClassLoader搜索的路径是由java.ext.dirs决定的。

System.out.println(System.getProperty("java.ext.dirs"));

结果为:

D:\Java\jdk1.5\jre\lib\ext

java.ext.dirs的内容是由java.exe选择的jre路径下面的 jre\lib\ext决定的。

最后一个是Bootstrap Loader了,它是由“sun.boot.class.path”决定的。

System.out.println(System.getProperty("sun.boot.class.path"));

结果为:

D:\Java\jdk1.5\jre\lib\rt.jar;D:\Java\jdk1.5\jre\lib\i18n.jar;D:\Java\jdk1.5\jre\lib\sunrsasign.jar;D:\Java\jdk1.5\jre\lib\jsse.jar;D:\Java\jdk1.5\jre\lib\jce.jar;D:\Java\jdk1.5\jre\lib\charsets.jar;D:\Java\jdk1.5\jre\classes

系统参数sun.boot.class.path需要在虚拟机启动前修改。作为java.exe的参数 -Dsun.boot.class.path = XXXXX

从名称可以看出 sun.boot.class.path sun.class.path 都是看到的路径,对该路径下面文件夹的东西就不管了。而sun.ext.dirs则告诉我们它会搜索dirs,也就是说如果指定目录没有搜索到就会搜索下层路径。

我们可以看出,使用dirs肯定是一个比较费时的操作,需要去逐层的搜索。

【说明】当JVM启动以后我们的这些参数是不能再修改的了,即使你使用System.setProperty方法也不行。因为在系统启动的时候读取以后就保存了一份。

前面说到了类加载器的结构,有这样一种结构有什么作用呢?下面来说明:

【类加载器可以看到parent加载器的所载入的所有类型,反之不行】

举例:加载Class的时候先是AppClassLoader被调用,AppClassLoader先请求它的parent加载器ExtClassLoader,ExtClassLoader也先请求它的parent加载器BootstrapClassLoader。如果BootstrapClassLoader能够加载就OK,以后都只能使用BootstrapClassLoader来加载,如果没有找到则返回ExtClassLoader来加载,如果没有找到就到AppClassLoader去搜索。

需要说明的是,如果你的Main所在的类是由ExtClassLoader加载的,如果后续加载的类不在ExtClassLoader和BootstrapClassLoader所在的路径下则会出现找不到类的异常,即使AppClassLoader能够搜索到也不行。

比方说:JDBC API的核心类都是使用BootstrapClassLoader来加载的,但是JDBC driver是由ExtClassLoader来加载的。其实不只是JDBC其他的java API都是这样的模式,估计是为了安全考虑。

但是这样岂不是会出现上面所说的找不到类的情况吗?有Context Class Loader来解决。

java.exe怎么找到jre

1.java.exe自己的路径下是否有JRE目录。

2.父目录下是否有JRE目录。

3.查找HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment下面的JavaHome指向的路径和RuntiemLib指向的jvm.dll(JDK6.0下面的注册表为例)

至于用的是哪个java.exe 找一下环境变量吧!

常见错误 不知道是哪个java.exe 调用了那个JRE里面的jvm.dll
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: