什么是类加载过程
JVM类加载流程为:加载——连接(连接又分为:验证,准备,解析)——初始化
各个步骤的含义
加载
将javac编译后的class文件加载到Java虚拟机的方法区中。
加载的过程分为三步:
第一步通过类的全限定名获取相应的二进制字节流,
第二步将二进制字节流转换为方法区可运行的数据结构,
第三步在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中这个类的访问入口。
连接
连接分为三步:
验证:主要是确保Class文件中的字节流中包含的信息是否符合jvm的规范,是否有安全隐患。主要验证四个方面:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备:为类变量(static修饰的静态变量)在方法区内分配内存,以及初始赋值。
解析:虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行。
初始化
正式给类变量赋正确的初始值。在准备阶段,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序制定的主观计划(也就是我们在代码中给变量赋的值)去初始化类变量和其他资源。
什么是类的生命周期
而对于类的生命周期又在此流程上增加了两个流程:加载——连接(连接又分为:验证,准备,解析)——初始化——使用——卸载
为了方便理解我们可以将这个流程想象为以下的内容:
张三存了好久的钱想买一个手机,于是他在网上买了一个IPhone13 Pro Max 1TB 远峰蓝手机,下单就是(加载);怀着激动心情等了几天终于收到新手机(链接);收到手机后我们肯定不能直接使用,还有一些使用前的流程,看看手机屏幕有没有刮痕,电池有没有鼓包等等(验证);如果没有问题,我们贴膜,把手机卡从旧手机中拿出来,放到新手机中等等(准备);然后开机,开机过程中会解析我们的手机卡是移动、联调亦或者电信(解析);正常开启后我们就需要对手机进行数据同步呀等等(初始化);等都操作完我们就可以开心的用新手机了(使用);过了些许时间,张三又买了其他的手机,于是就把这个手机给卖了(卸载),至此该手机的使命就完成。
什么是双亲委派机制
当一个类加载器收到了类加载请求,不会直接去加载,而是将这个请求委派给父类,依次类推,请求都会被委派给顶级父类启动类的类加载器中,当无法完成请求(在其搜索范围内无法找到对应的类)才会将请求还给子类来加载,依次类推,直到完成请求或各个等级的类都无法完成请求而报错。
类加载源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1、检查请求的类是否已经被加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 2、将类加载请求先委托给父类加载器
if (parent != null) {
// 父类加载器不为空时,委托给父类加载进行加载
c = parent.loadClass(name, false);
} else {
// 父类加载器为空,则代表当前是Bootstrap,从Bootstrap中加载类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 3、在父类加载器无法加载的时候,再调用本身的findClass方法来进行类加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
为什么要使用双亲委派机制
使用双亲委派机制的好处是给类加载制定了一个规则,使其能够确定优先级,以及层次间的关系。如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。