加载是将class文件加载到 jvm内存,并为之创建一个大的Class,任何类被系统使用都会建立一个大Class对象。Class用来表示文件内容,成员变量,构造方法,成员方法
代码语言:javascript复制连接:
验证是否是正确的Class内部结构
准备负责为静态成员分配内存,并设置默认初始值
解析将类的二进制符号符号替换为直接引用(例如地址值,替换为直接指向 )
类的加载时间
1.创建类实例时 new。
2.为静态[变量]赋值,访问静态变量
3.调用静态方法
4.使用反射方法创建某个类或接口的反射方法java.lang.Class
5.初始化某子类
6.通过java.exe调用类的方法时
类加载器
根类加载器
扩展类加载器
系统类加载
其他加载方式都是属于被动加载,不会导致类的加载和初始化 例如: 构造类的数组
代码语言:javascript复制Simple[] simples = new Simple[10];
新建了一个Simple数组,但是并不会导致Simple类的初始化,该操作只是在内存堆中开辟了一段连续地址空间.
引用静态[常量]不会导致该类的初始化.
类的加载阶段
类的加载就是将class的二进制读取到内存中,然后将字节流静态存储结构转换为方法去中运行的数据结构,并在堆中生成该类的Class对象,作为入口.
类加载的最终就是堆中的一个对象,不管被加载多少次,对应堆中对象始终是同一个, 在栈中形成对对象的引用.
类的连接阶段
代码语言:javascript复制public class LinkedPrepare {
private static int a = 10;
private final static int b = 10;
}
static int a = 10 在准备阶段不是10 ,而是初始化的值0,当然final static int b 则仍然是10,因为被final修饰的静态变量不会导致类的初始化
类初始化
类的初始化阶段所有的变量都会被赋予正确的值,也就是在编写程序时指定的值
静态语句块只能对后面的静态变量进行赋值,但是不能对其进行调用,否则无法通过编译.
代码语言:javascript复制public class classInit{
static{
System.out.print(x);
x = 1000;
}
private static int x = 10;
}
类变量的赋值顺序和静态代码块的执行代码,编译器的收集顺序,由在源文件中出现顺序决定. 父类的静态变量总能优先与子类的赋值
静态代码块保证了在同一时间只会被执行一次
代码语言:javascript复制public class Singleton{
// 1
private static int x = 0;
private static int y;
// 2
private static Singleton instance = new Singleton();
private Singleton(){
x ;
y ;
}
}
初始化结果语句放在1处,和2处的结果不同 放在2 处连接阶段, x = 0, y = 0, instaance = null; 初始化类阶段按顺序赋值真实初始化值, x = 0, y = 0, instance = new Singleton()调用构造方法 x = 1, y = 1
但是放在1 处 ,连接阶段 x = 0, y = 0 ,instance = null; 初始化阶段 instance = new Singleton() ,x = 1,y = 1 , 按顺序赋值 x = 0 ,y不变 y = 1;
类加载器
类加载器之间严格遵守父委托机制
- Bootsrap 根加载器
主要负责核心类库的加载,例如java.lang 包加载
- 扩展类加载器
扩展类加载器的父类是根加载器,主要用于加载java_home 下的jrelibext里面的类库,java实现,可以将自己实现的类打包放到下面加载.
- 系统类加载器
负责加载classpath下的类库资源
双亲委托机制
当一个类加载器调用loadclass之后,它并不会直接将其进行加载,而是先交给当前类加载器的父加载器尝试加载直到最顶层的父加载器,然后再依次向下进行加载.
线程上下文类加载器
JVM类加载器双亲委托机制的自身缺陷,JDK提供了很多SPI,例如JDBC,JNDI接口,具体的实现有各个不同的厂家进行提供,想要更换连接器,只需要切换即可,但是例如JDBC的接口是由根加载器进行加载,但是第三方驱动则由系统类加载器加载,如何解决问题,通过 Thread的上下文加载器,允许子委托机制进行加载.