一.归纳
Java中所有类加载的过程都是按照加载、验证、准备、初始化、卸载这几个步骤开始的 , 而解析则不一定, 当遇到动态绑定或者晚期绑定的情况下 , 可以在初始化之后再开始 .
虚拟机把描述类的信息从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型 . 这就是类加载机制 .
在Java中,类型的加载、连接和初始化过程都是在程序运行期间完成的 . 在加载的过程中虽然会稍微增加一定的性能开销 , 但是会为Java程序提供更高的灵活性 . Java中天生可以动态扩展的语言特性就是依赖运行期间动态加载和动态链接的特性实现的 .
当遇到以下5中情况时必须对类进行初始化(主动引用) .
1. 当new , getStatic , putStatic , invokeStatic这4种情况下时 , 如类没有进行初始化 , 则先触发初始化.
最常见的场景就是 : 使用new关键字实例化对象的时候、读取或者设置一个类的静态字段(final修饰的或已在编译器就放入常量池的字段除外),以及调用一个类的静态方法
2. 使用Java.lang.reflect包的方法进行反射调用的时候 . 如类没有进行初始化 , 则先触发初始化.
3. 初始化一个类的时候,若父类还未初始化 , 则先去初始化父类 . (接口不同 : 接口初始化时 , 并不要求父类接口全部完成初始化,只有在真正使用到父接口的时候才开始初始化父类)
4. 虚拟机启动时, 即项目启动时 , 用户需指定一个执行主类(包含main方法的类).虚拟机会先初始化这个类
5. 使用动态语言支持时 ,
注 : 以上情况被称为主动引用 , 其他所有的方式都为被动引用 . 且不会触发初始化 .
被动引用 :
1. 当使用子类直接调用父类的公有静态字段时 , 只有直接定义该字段的类[父类]才会初始化 , 子类不会初始化 .
2. 当定义引用类的数组时 , 不会触发该类的初始化 .
3. 类中的常量(静态final的)在编译阶段会存入静态常量池中 , 本质上并没有直接定义该常量的类 , 此时也不会触发初始化 .
要清晰一点 : 加载是类加载过程中的一部分.该阶段 , 虚拟机主要完成 :
a. 通过一个类的全限定类名(包名+类名)来获取定义此类的二进制字节流 .
b. 将这个字节流所代表的静态存储结果转化为方法区的运行时数据结构
c. 在内存中生成一个代表这个类的Java对象 . 作为访问入口 .
虚拟机并不要求二进制流一定从.class文件中获取,且虚拟机并没有指明要求从哪获取 . 除从Class文件中获取外 , 虚拟机也允许以下几种情况 :
注意 : 开发过程中 , 可通过重写classLoader的loadClass方法自定义自己的类加载器去控制获取二进制字节流的方式 .
数组本身也是一种引用类型 , 本身不通过类加载器创建 , 数组是通过虚拟机直接创建的 .但数组与类加载器仍有密切关系 , 因为数组的元素类型最终是依赖类加载器创建 .
一个数组类创建遵循以下原则 :
加载完成后 , 虚拟机外部的二进制字节流将按照虚拟机所需格式存储在方法区中 , ,然后在内存中实例化一个Java对象
加载阶段与连接阶段的部分内容(如验证)是交叉进行的 .
验证是连接阶段的第一步 , 为确保Class文件的字节流中的信息符合当前虚拟机的要求 . 且不会危害虚拟机自身安全需对信息进行验证 .
验证阶段主要完成以下4个阶段的动作 :
这一步主要验证字节流是否符合Class文件格式规范及是否能被当前版本的虚拟机所处理 . 主要包括 :
CAFEBABE
)主要对字节码信息进行语义分析,保证其信息符合Java规范,主要包括 :
主要是对类的方法体进行检验分析 . 主要工作有 :
该动作将在连接的最后一个阶段----解析时发生 .主要内容:
该阶段是正式为变量(仅包括static修饰的变量)分配内存并设置类变量初始值(为默认的零值)的阶段 , 这些变量所使用的内存都将在方法区中进行分配 .
此处仅包括类中定义的被static修饰的变量,不包括实例变量 , 实例变量将会在对象实例化的时候随着对象一起被分配在Java堆内存中 .
如 : 一个类中定义有public static int a = 100; 那么在准备过后的a = 0 ; 而a = 100是在程序被编译后 , 存放在类构造器<clinit>方法中.即a=100的赋值是在初始化阶段进行的
若a被final修饰符修饰 , 即: public static final int a = 100; 那么该字段的属性将存为constantVlaue属性,在准备阶段虚拟机就会为a赋值为100.
该阶段是虚拟机将常量池中的符号引用替换为直接引用的过程
以一组符号来描述所引用的目标 , 符号可以是任何形式的字面量.是要在使用时无歧义的可以定位到目标即可,其余虚拟机的内存布局无关 .
是可以直接指向目标的指针,相对偏移量或者是能间接定位到目标的句柄 . 其和虚拟机的内存布局相关,如果有直接引用 , 那么该引用的目标已经在内存中存在 .
类加载的最后一步 , 将以上经过加载、验证、准备、解析的字节码文件加载为Java对象 .
对于Java中的每一个类 , 都需要加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性. 每一个类加载器都有一个独立的类名称空间 .
即: 比较两个类是否相等 , 只有这两个类是由同一个类加载器加载的前提下才有意义 . 否则即便同一个虚拟机环境下 , 两个类也绝不相同 .
细分而言 , 类加载器分三种:
ava中系统属性java.ext.dirs指定的目录由ExtClassLoader加载器加载,如果程序中没有指定该系统属性(-Djava.ext.dirs=sss/lib)那么该加载器默认加载$JAVA_HOME/lib/ext目录下的所有jar文件,通过程序来看下系统变量java.ext.dirs所指定的路径
原则 :
除了顶层的BootStrapClassLoader外 , 所有的类加载器都会有自己的父类加载器 , 且加载器之间一般不会以继承的方式实现 , 而是通过组合的方式 .
工作原理:
如果一个类加载器收到加载请求 , 先把请求委派给父类完成,因此所有的加载请求都会上传到父类加载器中,只有当父类加载器无法完成时 , 才有子类加载器尝试自己去加载 .
优势:
Java类随着类加载器一起具备了一种带优先级的层级关系
原文:https://www.cnblogs.com/kxkl123/p/14136043.html