在之前的时候第一次知道了java的反射的概念。
其应用的是java的类加载器, java的类加载器在加载一个类文件为字节码文件的时候有这样7个过程
类在被加载到被卸载经过的步骤
加载(loading) ->验证(Verification)->准备(Preparation)->解析(Resolution)->初始化(Initiallization)->使用(using)->卸载(unloading)
其中 验证 准备 解析 这三个过程统称为链接(linking)
这七个阶段的发生顺序如下
其中,加载,验证, 准备,初始化,卸载的顺序是确定的, 但是解析的时间并不确定,这是因为java的动态绑定 ->即类可以在初始化之后再进行解析。也可以在准备阶段进行解析。
那什么时候类开始被加载呢? 这一点java虚拟机并没有去严格的规范,
但是却对类的初始化做了严格的控制
初始化:
1:在访问除常亮外的类的静态变量的时候
因为final定义的常量 会直接被插入字节码文件中,编译器会把他当做value 而不是field(域)
这是一种很有用的优化, 也称作内联。
2:在调用 new 来创建类的对象的时候
3:访问类的静态方法
4:反射
5:在初始化一个类的时候,如果发现其父类还未初始化,那就会先加载其父类
6:虚拟机启动的时候,会首先加载main方法的哪个类
这六种被称作主动引用
另外,接口的初始化跟类的初始化不太一样
因为接口中无法定义static块 所以当一个接口被初始化的时候,并不要求去初始化其父类 只有真正用到父接口的时候(例如引用接口中定义的常量) 才会初始化。
子类调用父类的静态变量的时候 父类会被初始化,而子类不会。也就是谁定义的初始化谁
通过数组引用 不会初始化
千万不要尝试在父类中引用子类
访问类的常量,不会初始化类
加载: 加载为类加载的第一个阶段 在此阶段,虚拟机主要完成一下三个动作
使用系统的类加载器 或者自定义的类加载器
1、 通过一个类的全限定名来获取定义此类的二进制字节流。
for example
<pre>
Class<?> ClassName = Class.forName("classPath");
</pre>
2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
也就是怎么把一个存储在磁盘上的文件变成可运行的状态
3、 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
这点很重要,java的反射就是用了这个 但是注意 hotspot Class 是在方法区中
加载阶段可能和Link阶段是交叉进行的, 可能加载过程中 类字节码的信息已经开始类型校验
2:验证
虚拟机检查二进制文件是否符合虚拟机要求
其实java本身是安全的,如果你想使用一个数组长度外的元素 或者转型成一个他没有实现的类的实例 编译器会报错。 但是别忘了 .class文件并不是只能由编译器产生。他的来源可以是jar 文件 或者是internet上下载下来的class
一共有四步。
文件格式验证 :验证是否可以由当前版本虚拟机处理 经过这步后,字节流才会进入内存的方法区中。 后面的三步都是在此基础上的
验证类
元数据验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。可能包括的验证如:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法……
验证方法
字节码验证:主要工作是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的
验证属性
符号引用验证 符号引用=>直接引用 这个动作发生在解析时
验证阶段并不一定必须要经过,可以通过命令关闭,加快虚拟机启动速度
准备阶段
准备阶段是为类的静态变量在方法区分配空间 和赋初始值的时候 不会对实例变量进行赋值 会在对象被实例化的时候伴随对象一起被分配到堆中;
for example public static int value =123;
这个阶段 value =0 在初始化阶段才会变成123
解析
解析阶段是将常量池里的符号引用变成直接引用的过程
符号引用: 用一组符号代表被引用的对象 可以是任何形式的字面量 只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用 : 直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。
也就是引用到物理地址
初始化
是类加载的最后一步 之前的几步 只有在加载的时候 用户可以选择用系统加载器还是自定义加载器 之后的几步都是由虚拟机来引导完成的
可以说 初始化的这一步 是真正与你所编写的代码相关的
初始化阶段是执行类构造器<(clinit())>方法的过程 <(clinit())>方法是由 编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。