有关类加载的一些知识

来源:互联网 发布:软件开发发展方向 编辑:程序博客网 时间:2024/04/30 16:50

1. 当一个Java程序开始运行时, 虚拟机会将一些必要的核心类加载到内存中。而核心类以外的类,则最好按需加载,也就是说在需要用到的时候加载。虽然这比一次性加载多出一些IO操作的开销,但是综合考量,从性能和速度上都优于一次性加载。比如,当我们定义一个对象的引用时(MyObject myObj),虚拟机并不在此时对MyObject进行加载,只有用到myObj = new MyObject()的时候,MyObject类才会被加载进入内存。

 

2. 类加载分为隐式和显式的两种,当我们使用new MyObject()的时候,使用了隐式的加载方法;而使用Class.forName()或ClassLoad中具有的loadClass作用的方法时,则是显式地加载了类。
    使用Class.forName(String className)和Class.forName(String className, boolean initialize, ClassLoader loader)方法,在底层实际上是调用了Class类的private static native Class forName0(className, true, ClassLoader.getCallerClassLoader())和private static native Class forName0(className, initialize, loader)方法。getCallerClassLoader()方法是个私有方法。
对比两个例子如下:
public class Office {
 public static void main(String[] args){
  try {
   Office off = new Office();
   System.out.println(1);
   Class c = Class.forName(args[0], true, off.getClass().getClassLoader());
   // Class c = Class.forName(args[0], false, off.getClass().getClassLoader());
   System.out.println(2);
   Object o = c.newInstance();
   System.out.println(3);
   Assemble a = (Assemble)o;
   System.out.println(4);
   a.start();
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
}
interface Assemble{
 public void start();
}
class Word implements Assemble{
 static{
  System.out.println("Word initialization!");
 }
 public void start(){
  System.out.println("Word start!");
 }
}
编译后运行java -verbose:class xxx.Office xxx.Word(xxx代表具体的包名)。
当initialize为true的时候,输出结果为:
[Loaded com.study.keywords.test.Office]
1
[Loaded com.study.keywords.test.Assemble]
[Loaded com.study.keywords.test.Word]
Word initialization!
2
3
4
Word start!
[Loaded java.lang.Shutdown from E:/j2sdk1.4.2/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from E:/j2sdk1.4.2/jre/lib/rt.jar]
而当initialize为false的时候,输出结果为:
[Loaded com.study.keywords.test.Office]
1
[Loaded com.study.keywords.test.Assemble]
[Loaded com.study.keywords.test.Word]
2
Word initialization!
3
4
Word start!
[Loaded java.lang.Shutdown from E:/j2sdk1.4.2/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from E:/j2sdk1.4.2/jre/lib/rt.jar]
可以看出,initialize决定了是否在加载类的时候就进行类的初始化。

 

3. 类加载器ClassLoader
    系统中有多个ClassLoader对象,而一个ClassLoader对象可以加载多个类的实例。所以,一旦我们获得一个类的Class对象,就可以调用该Class对象的getClassLoader()方法来获取加载这个类的加载器,并可以在之后的操作中使用这个类加载器来对其它的类进行显式的加载。
    而要获取一个类的Class对象,可以用SomeClass.class或new SomeClass().getClass()两种方式来操作。

 

4.使用URLClassLoader的一个例子
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoaderTest {
 public static void main(String[] args){
  try {
   URL u = new URL("file:/d:/my/lib/") ;
   URLClassLoader ucl = new URLClassLoader(new URL[]{ u }) ;
   Class c = ucl.loadClass(args[0]) ;
   Assembly asm = (Assembly) c.newInstance() ;
   asm.start() ;
   URL u1 = new URL("file:/d:/my/lib/") ;
   URLClassLoader ucl1 = new URLClassLoader(new URL[]{ u1 }) ;
   Class c1 = ucl1.loadClass(args[0]) ;
   Assembly asm1 = (Assembly) c1.newInstance() ;
   asm1.start() ;
  } catch (MalformedURLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InstantiationException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (Exception e){
   e.printStackTrace();
  }
 }
}
class Assembly{
 public void start(){
  System.out.println("Assembly started!");
 }
}

 

5.系统类加载器为AppClassLoader。

 

6. JAVA虚拟机的类加载原理
    当我们在命令行输入java xxx.class的时候,java.exe根据某种逻辑找到JRE,接着找到位于JRE中的jvm.dll(真正的java虚拟机),最后加载这个动态链接库,启动java虚拟机。
    虚拟机启动后,会先做一些初始化的动作,比方说获取系统参数等。一旦初始化动作完成,就会产生第一个类加载器,即所谓的Bootstrap Loader,Bootstrap Loader是用C++写成的(正因为如此,从java虚拟机的角度来看,逻辑上,Bootstrap Loader并不存在,所以在java代码中试图打印它的内容时,我们会看到输出为null),这个Bootstrap Loader所做的初始工作中,除了一些基本动作外,最重要的就是加载定义在sum.misc命名空间下的Launcher.java中的ExtClassLoader(因为是inner class,所以编译之后会变成Launcher$ExtClassLoader.class),并将其Parent设置为null,代表其父加载器为Bootstrap Loader。然后Bootstrap Loader再加载定义于sum.misc命名空间下的Launcher.java中的AppClassLoader(inner class,编译后为Launcher$ClassLoader.class),并将其Parent设置为之前生成的ExtClassLoader对象。这里需要注意的是,Launcher$ExtClassLoader.class与Launcher$AppClassLoader.class都是由Bootstrap Loader所加载,所以Parent和由哪个类加载器加载没有关系。
    这个BootstrapLoader==>ExtClassLoader==>AppClassLoader的加载关系,就是所谓的“类加载的层级体系结构”,在系统调用类加载器进行类的加载时,使用委派模式(向上级委派)。当调用的类加载器处在某一级时,加载新类会先委派上一级的加载器,在上一级加载器的URL指定的路径中加载类,如果找不到类,则还由当前类加载器在自己URL指定的路径中按照“找到就加载,找不到就抛异常”的原则来加载。
    另外需要注意的是,AppClassLoader和ExtClassLoader都是URLClassLoader的子类,所以他们都需要作为搜索类的位置的URL的引用。由原代码中可知:
    AppClassLoader使用的是系统的java.class.path参数值,该值是执行java.exe时,由-cp/-classpath参数或者系统的CLASSPATH环境变量的取值决定的,且-cp/-classpath的遵循优先级要比系统环境变量高。
    ExtClassLoader使用的是系统的java.ext.dirs参数值,默认情况下它指向%JRE%/lib/ext目录下。而在命令行中,我们也可以使用-Djava.ext.dirs=X:/yyy/zzz来指定。
    Bootstrap Loader使用的是系统的sun.boot.class.path参数值。

原创粉丝点击