转载请注明原帖地址:http://www.cnblogs.com/dongxiao-yang/p/5369195.html
java代码编译后产生的文件是各种Class字节码文件,这些文件都需要被jvm虚拟机加载到内存中才可以运行。从类被加载到jvm内存开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)7个阶段。如下图所示:
一 类加载的时机
类加载过程的第一个阶段:加载(Loading),什么时候开始,jvm规范并没有强制约束。但是对于阶段初始化(Initialization),虚拟机严格规定了有且只有5种情况必须对类进行初始化,显然在初始化前加载(Loading)阶段一定会先执行,所以这几种情况一定会调用类加载。
1.遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)时、以及调用一个类的静态方法的时候。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要触发父类的初始化。
4.当虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
5.当使用JDK1.7的动态语言支持时,如果一个java.lang.invokeMethodHandle实例的最后解析结果是REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
二 类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的模块被称为"类加载器"。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话说的更通俗一些:比较两个类是否“相等”,只有在这两个类由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个class文件,同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不同。
测试代码
1 package com.youku.data.mr.too.jvm;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5
6 public class ClassLoaderTestLL {
7
8 public static void main(String[] args) {
9
10 ClassLoader mycl = new ClassLoader() {
11 @Override
12 public Class<?> loadClass(String name)
13 throws ClassNotFoundException {
14 // TODO Auto-generated method stub
15 //return super.loadClass(name);
16 String filename = name.substring(name.lastIndexOf(".")+1)+".class";
17 //System.out.println(filename);
18 InputStream is =getClass().getResourceAsStream(filename);
19 if (is == null) {
20 System.out.println("parent :"+filename);
21 return super.loadClass(name);
22 }
23 byte[] b = null;
24 System.out.println("son :"+filename);
25 try {
26 b = new byte[is.available()];
27 is.read(b);
28 } catch (IOException e) {
29 // TODO Auto-generated catch block
30 e.printStackTrace();
31 }
32
33 return defineClass(name, b, 0, b.length);
34 }
35 };
36
37 Object obi;
38 try {
39 obi = mycl.loadClass("com.youku.data.mr.too.jvm.ClassLoaderTestLL").newInstance();
40 System.out.println(obi.getClass());
41
42 System.out.println(obi instanceof com.youku.data.mr.too.jvm.ClassLoaderTestLL);
43
44 } catch (InstantiationException e) {
45 // TODO Auto-generated catch block
46 e.printStackTrace();
47 } catch (IllegalAccessException e) {
48 // TODO Auto-generated catch block
49 e.printStackTrace();
50 } catch (ClassNotFoundException e) {
51 // TODO Auto-generated catch block
52 e.printStackTrace();
53 }
54
55
56 }
57
58 }
这段代码主要是参考了深入jvm虚拟机里面的代码,不同的是在一些地方增添了输出日志,结果如下。
son :ClassLoaderTestLL.class
parent :Object.class
parent :Throwable.class
parent :InstantiationException.class
parent :IllegalAccessException.class
parent :ClassNotFoundException.class
parent :ClassLoader.class
son :ClassLoaderTestLL$1.class
class com.youku.data.mr.too.jvm.ClassLoaderTestLL
false
不出意料的
System.out.println(obi.getClass());
System.out.println(obi instanceof com.youku.data.mr.too.jvm.ClassLoaderTestLL);
这两句输出的结果是类不相等。
加了
System.out.println("parent :"+filename);
System.out.println("son :"+filename);
这两段输出很清晰的显示了哪些类调用的新loadclass里的inputstream过程。
三 双亲委派模型
从java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用c++实现,是虚拟机自身的一部分;另一种就是所有其它的类加载器,这些加载器都是有java语言实现,独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。也可以细化为3种类加载器。
上图加载器之间的这种层次关系称为双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承的关系来实现,而都使用组合关系老复用父加载器的代码。
这个可以从ClassLoader这个类的源代码里看出来
1 */
2 public abstract class ClassLoader {
3
4 private static native void registerNatives();
5 static {
6 registerNatives();
7 }
8
9 // The parent class loader for delegation
10 // Note: VM hardcoded the offset of this field, thus all new fields
11 // must be added *after* it.
12 private final ClassLoader parent;
其中的parent是以一个类内部对象的形式出现的
双亲委派模型的工作过程是:如果一个类加载器收到类类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。
为了维持双亲委派模型的原则,目前ClassLoader的loadclass方法代码为
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 try {
10 if (parent != null) {
11 c = parent.loadClass(name, false);
12 } else {
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
23 long t1 = System.nanoTime();
24 c = findClass(name);
25
26 // this is the defining class loader; record the stats
27 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
28 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
29 sun.misc.PerfCounter.getFindClasses().increment();
30 }
31 }
32 if (resolve) {
33 resolveClass(c);
34 }
35 return c;
36 }
37 }
所以jdk1.2以后已经不提倡用户自定义classloader的时候自己重写loadClass()方法,应当把自己的类加载器逻辑写到findClass()方法中去,在loadClass()如果父类加载失败,则会调用自己的findClass()来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派规则的。
参考资料:深入理解java虚拟机 第二版