类加载全过程

来源:互联网 发布:php完全自学手册下载 编辑:程序博客网 时间:2024/05/18 02:02
JVM判断并装载类的过程

类从.java文件到实际加载到内存的过程:
.java文件 -> 通过你的JDK环境相关指令编译 -> .class文件 -> JVM初始化之后,如果有类的执行、调用等相关操作,JVM就会将.class文件加载到内存中,并开始下面的一系列处理:(链接->初始化)

一.关于ClassLoder
ClassLoader是Java用于加载类的一个机制。等到程序运行时,JVM先初始化,在JVM初始化的过程中,JVM生成几个ClassLoader,JVM调用指定的ClassLoader去加载.class文件等各类路径、文件的类。
1.程序运行时类的加载实际过程
JDK执行指令去寻找jre目录,寻找jvm.dll,并初始化JVM;
产生一个Bootstrap Loader(启动类加载器);
BootstrapLoader自动加载ExtendedLoader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。
BootstrapLoader自动加载AppClassLoader(系统类加载器),并将其父Loader设为Extended Loader。
最后由AppClassLoader加载HelloWorld类。

2.各种ClassLoader及其特点
BootstrapLoader(启动类加载器):加载System.getProperty("sun.boot.class.path")所指定的路径或jar
ExtendedLoader(标准扩展类加载器ExtClassLoader):加载System.getProperty("java.ext.dirs")所指定的路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld
AppClassLoader(系统类加载器AppClassLoader):加载System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld

特点:
ExtClassLoader和AppClassLoader在JVM启动后,会在JVM中保存一份,并且在程序运行中无法改变其搜索路径。如果想在运行时从其他搜索路径加载类,就要产生新的类加载器。
运行一个程序时,总是由AppClassLoader(系统类加载器)开始加载指定的类
在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载
BootstrapLoader(启动类加载器)是最顶级的类加载器了,其父加载器为null

3.各类ClassLoader的关系图解

4.关于不同类加载器多出命名空间不同的问题理解

二.类的加载方式
方式一:命令行启动应用时候由JVM初始化加载
方式二:通过Class.forName()方法动态加载(默认会执行初始化块,但如果指定ClassLoader,初始化时不执行静态块 )
方式三:通过ClassLoader.loadClass()方法动态加载(不会执行初始化块 )

三.详细分析下整个类的加载流程
1.类从编译,被使用,到卸载的全过程
编译 -> 加载 -> 链接(验证+准备+解析)->初始化(使用前的准备)->使用-> 卸载

2.类的初始化之前
加载(除了自定义加载)和链接的过程是完全由jvm负责的,包括:加载 -> 验证 -> 准备 -> 解析

1.首先是加载:
此过程由类加载器完成
这一块JVM要完成3件事:
1.通过一个类的全县定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在Jav堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
从class文件来->一般的文件加载
从zip包中来->加载jar中的类
从网络中来->Applet
获取二进制流获取完成后会按照jvm所需的方式保存在方法区中,同时会在java堆中实例化一个java.lang.Class对象与堆中的数据关联起来。

2.然后是验证:
一句话:检查代码的:完整性、正确性、安全性
主要经历几个步骤:文件格式验证->元数据验证->字节码验证->符号引用验证
文件格式验证:验证字节流是否符合Class文件格式的规范并 验证其版本是否能被当前的jvm版本所处理。ok没问题后,字节流就可以进入内存的方法区进行保存了。后面的3个校验都是在方法区进行的。
元数据验证:对字节码描述的信息进行语义化分析,保证其描述的内容符合java语言的语法规范。
字节码检验:最复杂,对方法体的内容进行检验,保证其在运行时不会作出什么出格的事来。
符号引用验证:来验证一些引用的真实性与可行性,比如代码里面引了其他类,这里就要去检测一下那些来究竟是否存在;或者说代码中访问了其他类的一些属性,这里就对那些属性的可以访问行进行了检验。(这一步将为后面的解析工作打下基础)
目的:确保class文件的字节流信息符合jvm的口味,不会让jvm感到不舒服。假如class文件是由纯粹的java代码编译过来的,自然不会出现类似于数组越界、跳转到不存在的代码块等不健康的问题,因为一旦出现这种现象,编译器就会拒绝编译了。但是,跟之前说的一样,Class文件流不一定是从java源码编译过来的,也可能是从网络或者其他地方过来的,甚至你可以自己用16进制写,假如jvm不对这些数据进行校验的话,可能一些有害的字节流会让jvm完全崩溃。

3.随后是准备:
一句话:为静态域分配存储空间
这阶段会为类变量(指那些静态变量)分配内存并设置类比那辆初始值的阶段,这些内存在方法区中进行分配。这里要说明一下,这一步只会给那些静态变量设置一个初始的值,而那些实例变量是在实例化对象时进行分配的。例如:
public static int value=123;此时value的值为0,不是123。
private int i = 123; 此时,i 还未进行初始化,因为这句代码还不能执行。

4.最后是解析:
一句话:符号引用 -> 直接引用
是对类的字段,方法等东西进行转换,具体涉及到Class文件的格式内容。

3.类的初始化条件(主动对类进行引用)
要对类进行初始化,代码上可以理解为‘为要初始化的类中的所有静态成员都赋予初始值、对类中所有静态块都执行一次,并且是按代码编写顺序执行’。
如下代码:输出的是‘1’。如果①和②顺序调换,则输出的是‘123’。
public class Main { public static void main(String[] args){ System.out.println(Super.i); } } class Super{ //① static{ i = 123; } //② protected static int i = 1; }

1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。
其实就是3种情况:
用new实例化一个类时
读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)
执行静态方法的时候。

2.使用java.lang.reflect.*的方法对类进行反射调用的时候,如果类还没有进行过初始化,马上对其进行。

3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。

4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。

5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。
【注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化】


Java类加载过程

1.静态代码块和静态变量的赋值 是 先于 main方法的调用执行的。
2.静态代码块和静态变量的赋值是按顺序执行的。
3.子类调用父类的类变量成员,是不会触发子类本身的初始化操作的。
4.使用new方式创建子类,对于类加载而言,是先加载父类、再加载子类(注意:此时由于父类已经在前面初始化了一次,所以,这一步,就只有子类初始化,父类不会再进行初始化)
5.不论成员块放在哪个位置,它都 先于 类构造方法执行。
0 0
原创粉丝点击