一、类加载过程
对于一个类来说,它的生命周期是这样的:
1.加载
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载Class
Loading 是不同的,一个是加载 Loading 另一个是类加载 Class Loading ,所以不要把二者搞混了。
在加载 Loading 阶段, Java 虚拟机需要完成以下三件事情:
1 )通过一个类的全限定名来获取定义此类的二进制字节流。
2 )将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3 )在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
2.验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节 流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。
验证选项:
文件格式验证
字节码验证
符号引用验证 ...
3.准备
准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段。
比如此时有这样一行代码:
public static int value = 123; //它是初始化 value 的 int 值为 0,而非 123。
4.解析
解析阶段是Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
5.初始化
初始化阶段,Java 虚拟机真正开始执行类中编写的Java 程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程。
二、关于类加载的典型试题
这段代码的输出结果是什么?
class A{ public A(){ System.out.println("A的构造方法"); } { System.out.println("A的构造代码块"); } static { System.out.println("A的静态代码块"); } } class B extends A{ public B(){ System.out.println("B的构造方法"); } { System.out.println("B的构造代码块"); } static { System.out.println("B的静态代码块"); } } public class test extends B{ public static void main(String[] args) { new test(); new test(); } }
解析:
我们的程序是从main开始执行的,那么main这里是test方法,所以,要执行main,就要先加载test。也就是说,在加载test类的时候,此时还没执行main。
我们的程序是从main开始执行的,那么main这里是test方法,所以,要执行main,就要先加载test。也就是说,在加载test类的时候,此时还没执行main。
test继承自B,要加载test,那么就要先加载B;
B继承自A,要加载B,那么就要先加载A;
总之,只要这个类被用到了,就要先加载这个类(实例化、调用方法、调用静态方法,被继承...都算被用到)。
然后,要想要构造test,就得先构造B,要想构造B,就得先构造A,那么对于A来说,构造过程 = 构造代码块的执行 + 构造方法的执行。
还有,静态的只执行一次,所以输出结果是:
三、双亲委派模型
双亲委派模型是类加载中的一个环节,这环节处于Loading阶段。
双亲委派模型,描述的就是JVM中的类加载器,如何根据类的全限定名( java.lang.String )找到.class文件的过程。
JVM里提供了专门的对象,叫做类加载器,负责进行类加载.当然找文件的过程也是类加载器来负责的。
.class 文件,可能放置的位置有很多.有的要放到JDK目录里,有的放到项目目录里,还有的在其他特定位置,因此JVM里面提供了多个类加载器,每个类加载器负责一个片区。
默认的类加载器主要是三个:
1.BootStrapClassLoader负责加载标准库中的类(String, ArrayList, Random, Scanner..)
2.ExtensionClassLoader负责加载JDK扩展的类.(现在很少会用到)
3.ApplicationClassLoader 负责加载当前项目目录中的类~~
其实程序猿还可以自定义类加载器,来加载其他目录中的类。Tomcat就自定义了类加载器,用来专门加载 webapps里面的.class 。双亲委派模型,就描述了这个找目录过程,也就是上述类加载器是如何配合的。
1.加载java.lang.String
a.程序启动,先进入ApplicationClassLoader类加载器
b.ApplicationClassLoader就会检查下,它的父加载器是否已经加载过了.如果没有,就调用父类加载器ExtensionClassLoader
c. ExtensionClassLoader也会检查下,它的父加载器是否加载过了.如果没有,就调用父类加载器BootStrapClassLoader
d.BootStrapClassLoader也会检查下,它的父加载器是否加载过,自己没有父亲~~于是自己扫描自己负责的目录
e. java.lang.String这个类在标准库中能找到! 直接由BootStrapClassLoader负责后续的加载过程.查找环节就结束了
2.加载自己写的test类
a.程序启动,先进入ApplicationClassLoader类加载器
b.ApplicationClassLoader就会检查下,它的父加载器是否已经加载过了.如果没有,就调用父类加载器ExtensionClassLoader
c. ExtensionClassLoader也会检查下,它的父加载器是否加载过了.如果没有,就调用父类加载器BootStrapClassLoader
d.BootStrapClassLoader也会检查下,它的父加载器是否加载过,自己没有父亲~~于是自己扫描自己负责的目录,如果没有扫描到,就回到子加载器继续扫描。
e.ExtensionClassLoader也扫描自己的目录,如果没有扫描到,就回到子加载器继续扫描。
f.ApplicationClassLoader也扫描自己负责的目录,能找到test类,于是进行后续加载,查找目录的环节结束。