JVM 类加载机制
先来一张图,盗的
加载
加载class对象,从JAR,WAR等路径加载class文件,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。
类加载的过程使用的是 双亲委派模式模型
简单的解释就是,子类在接收类加载请求的时候,优先使用父类的加载器,父类没有的时候,才会给子类加载,如果有直接放回。 主要解决的问题就是 防止篡改JDK原始类。如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。例如不能定义一个 java.lang的包名的类/
其中:Bootstrap 启动类加载器,加载<JAVA_HOME>/lib 里面的jar,
Extension 扩展类加载器。加载<JAVA_HOME>/lib/ext 里面的jar
Application 应用类加载器 加载用户自定义JAR
验证
这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。就是校验文件的可用性
准备
正式为类变量,类方法(static)分配内存并设置类变量的初始值,这些内存都在方法区内分配。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程
简单的说就是,将原本使用符号代替的常量,直接使用 内存地址
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码
这么说类初始化有点不好理解。抄了别人的几个例子解释起来比较容易理解
package com.jvm;
public class B {
static int i = 1;
static String staicStr = "staicStr";
static final String staicFinalStr = "staicFinalStr";
static {
System.out.println("B1 static:i=" + i);
}
static {
i++;
System.out.println("B2 static:i=" + i);
}
int j = 1;
public B() {
i++;
j++;
System.out.println("b: i=" + i + ",j=" + j);
}
{
i++;
j++;
System.out.println("b 非静态 i=" + i + ",j=" + j);
}
public static void cc() {
System.out.println("B 静态 static cc");
}
}
package com.jvm;
public class A extends B {
static int i = 1;
static {
System.out.println("A1 static:i=" + i);
}
static {
i++;
System.out.println("A2 static:i=" + i);
}
int j = 1;
public A() {
// super();
i++;
j++;
System.out.println("a: i=" + i + ",j=" + j);
}
{
i++;
j++;
System.out.println("a 非静态 i=" + i + ",j=" + j);
}
public void test() {
i++;
System.out.println("a test i=" + i);
}
public static void test1() {
i++;
System.out.println("a static test i=" + i);
}
}
1. 第一次执行
package com.jvm;
public class aa {
public static void main(String[] args) {
A a = new A();
a.test();
}
}
结果:
B1 static:i=1
B2 static:i=2
A1 static:i=1
A2 static:i=2
b 非静态 i=3,j=2
b: i=4,j=3
a 非静态 i=3,j=2
a: i=4,j=3
a test i=5
父类是B,子类是A,A初始化后执行了那些方法
- 按顺序执行B 的静态方法,
- 按顺序执行A 的静态方法
- 执行B的非静态块代码
- 执行B的构造函数
- 执行A的非静态块代码
- 执行A的构造函数
- 最后执行A的方法
两者变量值是独立的,互不影响
2.第二次执行
package com.jvm;
public class aa {
public static void main(String[] args) {
A.test1();
}
}
结果:
B1 static:i=1
B2 static:i=2
A1 static:i=1
A2 static:i=2
a static test i=3
只执行A的静态方法
- 按顺序初始化 B的静态方法
- 按顺序初始化A的静态方法
- 执行A的静态方法
非静态方法都没有执行
3. 第三次执行
package com.jvm;
public class aa {
public static void main(String[] args) {
A.cc();
}
}
结果:
B1 static:i=1
B2 static:i=2
B 静态 static cc
执行A.cc 方法,但是cc方法是父类B的静态方法
- 按顺序执行B的静态方法
- 执行B的静态方法CC
没有初始化A的 静态和 非静态还有 构造函数
第四次执行
package com.jvm;
public class aa {
public static void main(String[] args) {
System.out.println(A.staicStr);
}
}
结果:
B1 static:i=1
B2 static:i=2
staicStr
这个结果和第三次结果一致,就不做说明
第5次
package com.jvm;
public class aa {
public static void main(String[] args) {
System.out.println(A.staicFinalStr);
}
}
结果:
staicFinalStr
这次执行的是A的父类,里面的一个 final 常量
发现直接打印了这个常量的值,静态方法和 非静态方法 都没有执行