类加载机制
类加载机制
学习思路参考
a)、类加载机制就是把我们编写好的class文件加载到内存模型中,了解这个后面试被问到“能否直接定义java.lang.String”或者“ClassNotFoundException出现在哪一步”就能很快回答上来并说明为什么
b)、类加载机制包括(载(装载) 连接【验证、准备、解析(可选)】 初始化 使用 卸载),即类的声明周期。加载过程使用的是加载器,核心就是双亲委派模型。
类加载机制
概述
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象(即实例对象),用来封装类在方法区内的数据结构,整个结构可以如下解:
【备注】对象属性的具体填充在后续步骤中,具体可以参照对象的创建过程
加载.class文件的方式
- 从本地系统中直接加载
- 通过网络下载 .class 文件(URLClassLoader)
- 从 zip、jar 等归档文件中加载 .class 文件
- 从专有数据库中提取 .class 文件
- 将 Java源文件动态编译为 .class 文件
类加载机制详解
- 类加载机制:加载(装载) 连接【验证、准备、解析(可选)】 初始化 使用 卸载
- 装载阶段基本动作:
- 通过类型的完全限定名,产生一个代表该类型的二进制数据流;
- 解析这个二进制数据流为方法区内的内部数据结构;
- 创建一个表示该类型的java.lang.Class类的实例
- 连接阶段(了解):
- 验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。
- 准备,Java虚拟机为类变量分配内存,设置默认初始值。
- 解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。
- 初始化:当一个类被主动使用时,即创建对象、使用静态方法或者字段,反射、初始化子类等等,Java虚拟就会对其初始化 。
- 参照范例了解一下类的加载过程:
class MyClass1 {
static {//静态块
System.out.println("static block ");
}
}
public class Step2Demo1 {
Class[] classArray = {
MyClass1.class//这样引用该类,必然需要将该类加载到虚拟机中
};
public static void main(String[] args){
//MyClass1 my = new MyClass1();
System.out.println("hello word");
//输入结果只有"hello word",没有"static block ""
}
}
类加载器
java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象.
两种类型的类加载器
- Java虚拟机自带的类加载器
- 根类加载器 (Bootstrap classloader)
- 使用c++编写,我们无法在java代码里面获得该类 【从系统属性sun.boot.class.path所指定的目录加载类库,比如java.lang.*】
- 扩展类加载器(Extension classloader)
- java编写 【从java.ext.dirs系统属性所指定的目录中加载类库或者从jdk的安装目录的jre\lib\ext子目录下(扩展目录)下加载类库】
- 应用程序类加载器(Application ClassLoader)
- java编写 【从环境变量classpath或者系统属性java.class.path所指定的目录下加载类库,用户自定义的类加载器的父类加载器】。该类加载器也称为系统类加载器(System ClassLoader),开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
- 根类加载器 (Bootstrap classloader)
- 用户自定义的类加载器
- java.lang.ClassLoader的子类,用户可以定制类的加载方式
类加载器的层次关系如下,即双亲委派模型:
类加载的父委托机制
JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
类加载器均是继承自java.lang.ClassLoader抽象类。
参照如下的代码:
public class TestClassLoader {
public static void main(String[] args) {
ClassLoader loader = TestClassLoader.class.getClassLoader();
System.out.println(loader.toString());
System.out.println(loader.getParent().toString());
System.out.println(loader.getParent().getParent());
}
}
观察打印结果:
sun.misc.Launcher$AppClassLoader@500c05c2
sun.misc.Launcher$ExtClassLoader@454e2c9c
null
第一行打印的是应用程序类加载器(默认加载器),第二行打印的是其父类加载器,扩展类i加载器,按照我们的想法第三行应该打印启动类加载器的,这里却返回的null,原因是getParent(),返回时null的话,就默认使用启动类加载器作为父加载器.
思考
- ClassNotFoundException解决方法
- 题目:
下面有关java类加载器,说法正确的是?
a.引导类加载器(bootstrap class loader):用来加载Java核心库,是用原生代码来实现的
b.扩展类加载器(extensions class loader):用来加载Java的扩展库
c.系统类加载器 (system class loader):根据Java应用的类路径(CLASSPATH)来加载Java类
d.tomcat为每个App创建一个Loader,里面保存着此WebApp的classLoader。需要加载WebApp下面的类时,就取出ClassLoader来使用
正确答案为:A,B,C,D
类的初始化执行顺序
- 静态变量、静态代码块初始化顺序级别一致,谁在前,就先初始化谁。从上而下初始化(只在类加载时,初始化一次)
- 非静态变量、非静态代码块初始化顺序级别一致,谁在前,就先初始化谁。从上而下初始化(只要对象实例化一次,就初始化一次)
- 构造方法在非静态变量、非静态代码块之后执行。
- 子类静态变量、静态代码块在父类的静态变量、静态代码块之后执行。
- 子类非静态变量、非静态代码块在父类构造方法之后执行。
- 子类构造方法在父类构造方法之后执行。
- 静态方法不会被子类重写。