虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载过程。
在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会领类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
类的生命周期:
加载、校验、准备、解析、初始化、使用、卸载
初始化阶段,虚拟机规则严格规定了有且只有5种情况必须对类进行初始化:
1/遇到new/getstatic/putstatic/invokestatic这4条字节码指令时,如果类没有初始化,则需要先触发初始化。
Java代码场景:使用new初始化对象,读取或设置一个静态字段,调用一个类的静态方法时。
2.使用java.lang.reflect包的方法对类进行反射调用时
3.当初始化一个类,发现其父类没有初始化时,要先触发其父类的初始化
4.当虚拟机启动时,有用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
5.当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析的结果REF_getStatic/REF_putStatic/REG_invokeStatic的方法句柄,并且这个方句柄对应的类没有进行过初始化,则需要先触发其初始化。
通过子类来调用父类中定义的静态字段,只会触发父类的初始化。
类加载过程
-
加载、验证、准备、解析、初始化
-
加载
- 1)通过类的全限定名来获取定义此类的二进制字节流
- 2)将这个字节流所代表的的静态存储结构转换成方法区的运行时数据结构
- 3)在内存生成一个代表这个类的java.lang.class对象,作为方法区这类的各种数据访问入口。
-
验证(很重要,但是不一定必要)
- 1)格式验证
验证字节流是否符合Class文件的格式规范,并且能被当前版本的虚拟机处理。
格式验证通过后,字节流会进入内存的方法区进行存储,后面的3个阶段都是基于方法区的存储结构进行。 - 2)元数据验证
对字节码的描述信息进行语义校验。如是否有父类,父类是否继承了不允许被继承的类... - 3)字节码验证
最复杂的阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
针对方法体进行校验分析。
jdk1.6优化:给方法体的Code属性的属性表增加了新属性StackMapTable,描述方法体中所有基础块开始时本地变量表和操作栈应有的状态。将字节码验证的类型推导转变为类型检查,节约时间。 - 4)符号引用验证
发生在虚拟机将符合引用转化为直接引用的时候,这个动作将在解析阶段发生。
- 1)格式验证
-
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量使用的内存在方法区分配。
注意:进行内存分配的仅包括类变量,不包括实例变量。
通常情况下,初始值都是0,;final修饰的情况直接赋值; -
解析
虚拟机将常量池中的符号引用替换为直接引用。
符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存。
直接引用和虚拟机实现的内存分布相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
解析动作主要针对:类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符 -
初始化
初始化阶段就是执行类构造器<clinit>()方法的过程。
类加载器
每个类加载器都有独立的类名称空间。
比较两个类相等,必须由同一个类加载器加载才有意义。
同一个class文件,被同一个虚拟机加载,若类加载器不同,两个类必然不相等。
双亲委派模型
如果一个类加载器收到了类加载请求,他首先不会自己去尝试加载这类,而是把这个请求委派给父类加载器去完成,只有当父类加载器反馈自己无法完成这个家在请求时(它的搜索范围中没有找到所需要的类),子加载器才会尝试自己去加载。
3种类加载器:
- 启动类加载器
BootStrap ClassLoader
加载范围:<JAVA_HOME>\lib 或 -Xbootclasspath参数指定的路径中的所有类库 - 扩展类加载器
Extension ClassLoader
加载范围:<JAVA_HOME>\lib\ext 或 被java.ext.dirs系统变量指定的路径中的所有类库 - 应用程序类加载器
加载范围:用户路径(ClassPath)上指定的类库
网友评论