1.类的加载图示
2.简述类的加载过程
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载、类的链接、类的初始化这三个步骤来对类进行初始化。如果不出现意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者初始化
*1.阶段一:加载-Loading
类的加载指的是将类的.class文件中的二进制数据读取到内存中,存放在运行时数据区的方法区中,并创建一个大的Java.lang.Class对象,用来封装方法区内的数据结构
*2.阶段二:链接-Linking
①. 验证:确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性
目的是确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
②. 准备:
为类变量分配内存并且设置该类变量的默认初始化值
这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化
这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量会随着对象一起分配到Java堆中
③. 解析:将常量池中的符号引号转换为直接引用的过程
解释什么是符号引号和直接引用
教室里有个空的位子没坐人,座位上边牌子写着小明的座位(符号引用),后来小明进来坐下去掉牌子(符号引用换成直接引用)
*3.阶段三:初始化-Initialization
①. 为类变量赋予正确的初始化值
②. 初始化阶段就是执行类构造器方法< clinit >()的过程。此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码快中的语句合并而来
简单代码实现:
public class ClassInitTest {
private static int num=1;
static{
num=2;
number=20;
System.out.println(num);
//System.out.println(number); 报错:非法的前向引用
}
//Linking之prepare: number=0 -->initial:20-->10
private static int number=10;
public static void main(String[] args) {
System.out.println(ClassInitTest.num);
System.out.println(ClassInitTest.number);
}
}
编译后,我们通过JclassLib插件来看看编译后的字节码class文件
③. 若该类具有父类,Jvm会保证子类的< clinit >() 执行前,父类的< clinit >() 已经执行完成。clinit 不同于类的构造方法(init)
简单代码实现:
public class ClinitTest1 {
static class Father{
public static int A=1;
static{
A=2;
}
}
static class Son extends Father{
public static int B=A;
}
public static void main(String[] args) {
//这个输出2,则说明父类已经全部加载完毕
System.out.println(Son.B);
}
}
输出为2,说明父类的clinit构造方法先执行。
3.类加载器的介绍
①. JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
②. 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范并没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
③. sum.misc.Launcher:它是一个java虚拟机的入口应用
④. 无论类加载器的类型如何划分,在程序中我们常见的类加载器始终只有3个,如下所示:
简单代码实现:
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取systemClassLoader的父类:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
//获取扩展类的父类:BootStrapClassLoader
ClassLoader bootStrapClassLoader = extClassLoader.getParent();
System.out.println(bootStrapClassLoader);
//获取自定义类的加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//获取String类的加载器
ClassLoader stringClassLoader = String.class.getClassLoader();
System.out.println(stringClassLoader);
}
}
执行结果:
从中我们看出:引导类加载器获取不到,其底层使用C、C++写的;对于自定义类来说,默认使用的是类加载器进行加载;string类以及java核心的类库都是用引导类加载器加载的
4.虚拟机自带的虚拟机
*1.启动类加载器/引导类加载器 —BootStrapClassloader
①. 这个类加载使用C/C++语言实现的,嵌套在JVM内部
②. 它用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar或sum.boot.class.path路径下的内容),用于提供JVM自身需要的类
③. 并不继承自java.lang.ClassLoader,没有父加载器
④. 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
⑤. 由于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
*2.扩展类加载器—ExtensionClassLoader
①. Java语言编写,由sum.music.Launcher$ExtClassLoader实现
②. 派生于ClassLoader类
③. 父类加载器为启动类加载器
④. 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
*3.应用程序类加载器/系统类加载器—SystemClassLoader
①. java语言编写,由sum.misc.Launcher$AppClassLoader实现
②. 派生于ClassLoader类
③. 父类加载器为扩展类加载器
④. 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
⑤.该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
⑥. 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
*4.用户自定义加载器
①. 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们换可以自定义类加载器,来定制类的加载方式
②. 如果获取ClassLoader
(1). 方式一:获取当前类的ClassLoader
Clazz.getClassLoader()
(2). 方式二:获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
(3). 方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
(4). 方式四:获取调用者的ClassLoader
DriverManager.getCallerClassLoader()
此次的讨论就到这里哈,欢迎小伙伴们留言讨论!