- 通过类的全限定名来获取类的二进制字节流
- 数组类本身不通过类加载器创建,由虚拟机直接创建
- 数组类的组件类型是引用类型,递归加载过程去加载
- 数组类的组件类型不是引用类型(int[]),虚拟机把数据与引导类加载器关联
- 把字节流的静态存储结构,转化成方法区的运行时数据结构
- 内存中生成对应的Class对象,作为这个类的访问入口(Hotspot放在方法区)
- 文件格式验证
- 文件版本是否在当前虚拟机处理范围内等,以保证字节流能被正确存储在方法区
- 元数据验证
- 是否有父类;是否非法继承了final类;是否实现了接口方法;是否与父类方法冲突等
- 字节码验证
- int类型数据按long类型加载到本地变量表;跳转指令跳转到方法体以外;错误的类型转换等对方法体校验
- 符号引用验证
- 为静态属性分配内存,并设置初始值
- int类型初始化为0,正式的赋值语句在【类构造器】里,即初始化阶段
- final int会赋真实值
- 虚拟机将常量池中的符号引用替换为直接引用
- 在初始化阶段最重要的事情就是对类变量进行初始化和执行静态块
- 解释:执行【类构造器】clinit()方法,该方法是编译器自动收集类变量赋值动作和静态块生成的
- 父类的【类构造器】会先于子类执行;父接口的【类构造器】不会先执行
- 多线程环境下【类构造器】的执行会加同步锁
- 对类变量指定初始值有两种方式:1、声明类变量时指定初始值;2、使用静态初始化块为类变量指定初始值
- 初始化的时机
- 1)创建类实例的时候,分别有:1、使用new关键字创建实例;2、通过java.lang.reflect包的反射调用;3、通过反序列化方式创建实例。4.Class.forName?
- 2)调用某个类的静态方法
- 3)读取或者设置某个类或接口的静态属性(被final修饰的除外)
- 4)初始化某个类的子类,其父类应该先被初始化。
- 5)直接使用java.exe命令来运行某个主类(包含main方法的类)。
- 不会触发类的初始化
- 通过子类调用父类的静态变量,只初始化父类,不会初始化子类
- 2、通过数组定义引用类(即定义目标类的数组),不会触发此类的初始化
- 3、引用常量时,不会触发该类的初始化
public static final String tt = "aaa"; //引用Sup.tt不会触发 public static final String tt; //引用Sup.tt会触发 static { tt = "bbb"; }
6. 类加载器
- 任何一个类需要和加载它的类加载器一起确立在虚拟机中的唯一性
- 类加载器
- 启动类加载器Bootstrap Classloader
- 是虚拟机的一部分,C++编写(其它都是java编写继承Classloader抽象类)
- 加载JAVA_HOME/lib下特定文件名的类库(例如rt.jar)到虚拟机内存
- 无法直接引用。自定义类加载器需要委派给Bootstrap,直接使用null代替即可
- 扩展类加载器Extention Classloader
- 由sun.misc.Launcher$ExtClassLoader实现
- 负责加载JAVA_HOME/lib/ext目录,或者java.ext.dirs指定目录下的所有类库
- 开发者可直接使用该类加载器
- 应用类加载器Application Classloader
- 由sun.misc.Launcher$AppClassLoader实现,也称为系统加载器
- 程序默认的类加载器,加载classpath上指定的类库
- 双亲委派模型-Paraents Delegation Model
- 除了启动类加载器,别的加载器都得有父加载器
- 工作过程:当一个类加载器收到加载请求,会先让父加载器尝试加载,只有父加载器无法加载时自己才会加载
- 代码ClassLoader.loadClass()
- 好处:假如用户自己写了一个java.lang.Object类,就会委派给Bootstrap去加载,保证了所有加载环境得到的是同一个类,否则会出现多个Object导致混乱
- 自定义类加载器无法加载java.lang开头的类,会跑出异常SecurityException:禁止的包名java.lang
- 破坏双亲委派 -- 没看