讲在前头:首先明确一下双类加载概念,双类加载常出现在父子类,即加载子类(子类中有继承父类方法),主要来说一下父类子类加载流程。
如下:
public class Father {
public static int a = 1;
static {
System.out.println(a);
}
}
public class son extends Father {
public static int b = 2;
static {
a = 3;
System.out.println(b);
}
static {
b = 4;
System.out.println(a);
}
public static void main(String[] args) {
Son s = new Son();
System.out.println(s);
}
}
emmm,既然要说加载,就先来说一下类加载过程吧!
类加载过程大面上是三个阶段:加载,连接,初始化
加载:通过一个类的全限定名来获取类的二进制流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,最后在内存中生成一个代表这个类的java.lang.Class变量,作为方法区这个类的各种数据的访问入口
连接:主要是将加载阶段产生的java.lang.Class变量进行从符号引用到直接引用的转变,其中有分了三个阶段:
-验证:验证阶段主要是验证加载进来的字节码文件有无被篡改,验证安全性
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
// 这里是重点,调用的是虚拟机的系统方法
if (sun.misc.VM.isSystemDomainLoader(loader)) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
-准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
-解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
初始化: 初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
上边大概说了一下类加载过程,你可能会疑惑,好像有点跑偏了,等我着补着补,接着往下看。
既然都说了类加载机制,你们肯定也都知道一些加载类的情况(时机),看看下面有没有你知道的?
1. 创建类的实例,也就是new一个对象
2. 访问某个类或接口的静态变量,或者对该静态变量赋值
3. 调用类的静态方法
4. 反射(Class.forName(“com.sun.approve”))
5. 初始化一个类的子类
6. JVM启动时标明的启动类,即文件名和类名相同的那个类
你看,这么一着补就回正道了吧,划重点-注意第五条,是不是很符合主题,没错,其实就是这样。
既然都回正道了,那就正道的光~。我们来说一下双类加载,父子类的加载过程。因为我们这边的类属性和方法都是静态方法,也就是类方法,会在创建类的时候一同创建出来。
执行main方法,断点看一下
主要断了上述几个位置,然后执行main方法,看一下效果
首先会加载父类,可以看到因为是静态属性且有赋值操作,所以静态代码块在执行的时候a有值为“1”,接下来F9看下个断点
我们实例的是子类对象,且继承链只有两段,所以会回到子类,静态属性有初始值,b为“2”,静态代码块中又重新对a进行了赋值,那么a会跟想象中的一样变成“3”吗?我们F9看下一步操作(这里注意一下,代码一般情况下按顺序执行,也有例外,如果你想知道评论区告诉我,下一次讲解一下)
会不会疑惑?我怎么记得静态变量的值不会被修改呢?那我只能说-记错了兄弟,因为大部分时候static都会伴随着final关键字一起使用,而被final关键字修饰的变量不可被修改-这里划重点:如果final关键字修饰的是基本类型变量是值不可以被修改,如果修饰的是引用类型变量是引用不可被修改,这点不能混淆。
好了,上边已经到了断点的最后一步,再F9一下就结束了,我们看一下结束后控制台的执行结果吧!
是不是和我们料想中的一样,不知道你好不好奇,反正我好奇,最后一个静态代码块里边我们将静态变量b的值修改成了4,那么在打印这个变量的时候,会打印出来一个什么值?我们就来尝试一下。注意这里的b是静态变量,可不能用对象来点啊!就直接用类来点就行了。
public static void main(String[] args) {
System.out.println(Son.b);
}
OK,看到上边控制台输出的最后一个,很清楚了吧. . .
双类加载讲到这里就结束了,emmm,感觉讲的有点糙,类加载过程这边只是稍微说了一下,更多深入的类加载机制讲解可以看下边的博客,我就不赘述了,好吗?好的!
https://juejin.cn/post/6844903564804882445
注意啊注意,再画一个重点,看这篇文章的时候看看评论区,可以收获新天地。