一、类加载
1、生命周期
(1)加载
通过一个类的全限定名来获取该类的二进制字节流。
将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象
该对象作为方法区这个类的各种数据的访问入口完成后,虚拟机外部的二进制字节流就按照虚拟机所需格式储存在方法区中
(2)连接
加载阶段未完成,连接阶段已经开始了,两者会交叉运行。
-
验证
为什么需要验证? 为了确保Class文件的字节流中包含的信息符合虚拟机的规范要求,因为Class文件可以用任何途径产生并不一定都来自java,字节流不符合Class文件格式的约束,虚拟机会抛出java.lang.VerifyError异常,所以为了保护虚拟机,所以需要验证。
验证阶段大致完成4个阶段的检验动作:文件格式验证,元数据验证,字节码验证,符号引用验证
-
文件格式验证 保证输入的字节流能被正确的解析并存储在方法区
-
元数据验证 确保符合Java语言规范
-
字节码验证 确定程序语义是合法的,符合逻辑的
-
符号引用验证 在指定类中是否存在符合方法的字段描述符号(解析阶段)
-
-
准备
为类的静态变量分配内存,初始化为系统的初始值。对于final static修饰的变量,直接赋值为用户的定义值。进行内存分配的只包括类变量(被static修饰的变量),并不包括实例变量,实例变量是在对象实例化时随对象一起分配在java堆中。
例如:
在准备阶段过后的初始值为0,而不是7,把a赋值为7的动作将在初始化阶段才会执行。但在准备阶段,则会将b赋值为8;
public static int a=7; public static final int b=8;
-
解析
解析是将常量池内的符号引用转为直接引用(如物理内存地址指针)
(3)初始化
是类加载过程的最后一步,到了此阶段,jvm才真正开始执行类中定义的java代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源。到初始化阶段,才真正开始执行类中的Java程序代码。
-
初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。这个()方法主要是给静态变量赋值和执行静态语句块。
-
为静态变量赋初值
- 定义静态变量时指定初始值。如 private static String x=“123”;
- 在静态代码块里为静态变量赋值。如 static{ x=“123”; }
-
如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成()方法
(4)使用
(5)卸载
可能出现的情况
-
执行了System.exit()方法
-
程序正常执行结束
-
程序在执行过程中遇到了异常或错误而异常终止
-
由于操作系统出现错误而导致Java虚拟机进程终止
2、类加载器
(1)启动类加载器
用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
(2)扩展类加载器
这个类加载器负责加载<JAVA_HOME>\lib\ext目录下的类库(JRE扩展目录),用来加载java的扩展库的指定的目录中的JAR包的类,开发者可以直接使用这个类加载器,由Java语言实现,父类加载器为null。
(3)应用程序类加载器
这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。一般情况下这就是系统默认的类加载器
(4)自定义类加载器
这个加载器可以满足我们加载类的特殊需求,需要继承java.lang.ClassLoader类并且覆盖其中的findClass()方法和defineClass()方法。
(5)双亲委派机制
Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。
-
如果一个类加载器收到类加载请求,它不会自己先加载,而是委托给父类的加载器去执行
-
如果父类加载器还存在父类加载器,则进一步委托,直到到达最顶层的类加载器
-
如果父类加载器可以完成类的加载任务,就成功返回,如果不能完成再回退到子类加载器判断
-
加载过程: 自定义类加载器 >> 应用程序类加载器 >> 扩展类加载器 >> 启动类加载器
为什么要用双亲委派机制?
- 不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar中,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。
- 即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,因为Java中的java.lang.Object类是由启动类加载器进行加载,自己编写的java.lang.Object类不会被启动类加载器进行加载,保证不会出现混乱。
- 相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。