java - 内存/类加载
类加载
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象.
- 链接:将java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类实例信息符合jvm规范,没有安全方面问题;验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证;
- 准备:为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量将不再此操作范围内);内存将在方法区内分配
- 解析:将常量池中所有的符号引用(常量名)转为直接引用(直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄)
- 初始化:初始化阶段是执行类构造器<clinit>()方法的过程
- 在连接的准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员制定的主观计划去初始化类变量和其他资源;类构造器<clinit>()方法是由编译期自动收集类中所有的类变量的赋值动作和静态代码块中的语句合并产生的.(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
类初始化发生时间
- 类的主动引用(一定会发生类的初始化)
- 虚拟机启动,先初始化main所在的类
- new一个对象
- 调用类的静态成员(final常亮除外)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 初始化一个类,如果父类没有被初始化,则先初始化父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个类才会被初始化.如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发类的初始化;Class[] arr = new Class[3];
- 引用常亮不会触发此类的初始化(常亮在链接阶段就存入调用类的常量池中了)
双亲委派机制
1、防止重复加载同一个.class
。通过委托去向父加载器查询,如果父加载器加载过了,就不需要再加载一遍。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全
package reflect;
public class ClassLoadDemo {
static {
System.out.println("main类 static被加载!");
}
public static void main(String[] args) throws Exception {
// S s = new S();
// 反射也会产生主动调用
// Class.forName("reflect.S");
// 子类调用父类静态变量不会产生类的引用方法
// System.out.println(S.b);
// S[] arr = new S[3];
// static变量在链接阶段已经设置完初始值
System.out.println(S.M);
}
}
class F {
static int b = 2;
static {
System.out.println("父类static被加载!");
}
public F() {
System.out.println("父类构造初始化!");
}
}
class S extends F {
static {
System.out.println("子类static被加载!");
m = 300;
}
public S() {
System.out.println("子类构造初始化!");
}
static int m = 100;
static final int M = 1;
}
class A {
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
static int m = 100;
public A() {
System.out.println("A类空参构造初始化");
}
}