深入理解JVM一加载机制

人若无名 便可专心练剑

引子

这里写图片描述
如图,这是java代码到最终执行程序的过程。

1.java代码——>静态编译(javac)——>byteCode(.class)(通常为静态编译,除了特殊情况,如动态代理)
2.Loading——>Linking(vertify—>prepare—>resolve)——>initialization——Memory——>Execution engine(运行时)

本节我们重点关注classLoader中的加载、连接、初始化过程。

类或接口的载入过程(class loading subsystem)

这里写图片描述

(以下所说的"类"等价于 java类,java接口)

Loading (加载)

“加载(Loading) ”是“类加载”(Class Loading)过程的一个阶段,希望读者没有混淆这两个看起来很相似的名词。
Loading中完成的工作:
1. 通过一个类的全限定名获取这个类对应的byteCode 二进制流。
思考:从哪里获取?
通常有如下几种方式:
- jar、war等压缩包文件中获取。
- 网络中获取二进制流数据。如applet程序
- 动态获取,运行时产生字节码,并载入。如动态代理
2. 将这个二进制流的byteCode格式转换为MethodArea的存储格式,放入MethodArea。
3. 在内存中生成一个对应的class对象,作为方法区这个类各种数据的访问入口。

简而言之:加载就是把字节码载入MethodArea,并生成对应Class对象,以供运行使用。

连接(Linking)

连接中的具体步骤不一定是要等loading完成才能进行,一般都会在loading开始运行后交叉执行连接动作。但是,loading与linking的开始时间是按照先后的顺序。

连接中有三个大的步骤:验证——>准备——>解析
一.验证 (verification)
验证 (verification)为了保证 class文件对应的字节码二进制流是否符合当前JVM运行的要求,不会对JVM造成危害。验验证失败抛出java.lang.VerifyError异常。

  1. 文件格式验证
    主要验证加载到的字节码流是否符合class文件的格式规范,并且验证是否能被当前版本的JVM处理(JVM向低版本兼容),
    如:
    是否以魔数0xCAFEBABE开头;
    主、 次版本号是否在当前虚拟机处理范围之内;
    常量池的常量中是否有不被支持的常量类型(检查常量tag标志);
    指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量;CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据;
    Class文件中各个部分及文件本身是否有被删除的或附加的其他信息…

文件格式验证成功,则会把class文件(bytecode)格式转换为MethodArea的特定格式结构,接下来的步骤都会在这个MethodArea格式基础上进行。

2… 元数据验证
主要是校验元数据是否符合JVM规范。
如:
这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类);这个类的父类是否继承了不允许被继承的类(被final修饰的类);
如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法;类中的字段、 方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等)

3… 字节码验证
主要是验证分析方法体。

4… 符号引用验证
可以看做是对常量池中对各个数据的引用是否能找到对应的数据。
如:
符号引用中通过字符串描述的全限定名是否能找到对应的类;
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段;
符号引用中的类、 字段、 方法的访问性(private、 protected、 public、 default)是否可被当前类访问;

通常这个阶段可能抛出的异常:

java.lang.IllegalAccessError、
java.lang.NoSuchFieldError、
java.lang.NoSuchMethodError

如果确认代码不需要验证(如,生成上的老代码),-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间

二.准备 (prepare)

准备阶段就是为static变量分配内存空间(MethodArea中)、并设置为初始值。

比如:

	static String bbve = "ABC";

在这个阶段会给bbve变量在methodArea中分配内存空间,并设置为初始值(null)。在这个阶段并没有将“ABC”赋值给变量。如果用了 static final修饰,会在准备阶段完成赋值。

三.解析 (resolve)
解析过程就是把class文件常量池中的符号引用(一部分)转换为内存中的直接引用的过程,这种解析完成的前提是在编译器就可以确定要执行的具体方法、具体类型。

初始化

初始化时class加载的最后一步,之前的步骤除了loading可以由用户自定义的classLoader加载之外,其余都是有JVM控制的。

初始化阶段开始真正的执行字节码,其实就是执行字节码的指令,执行static变量的赋值操作、执行static块;

并且这个阶段,由JVM自身保证各个class的同步(如果多
个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法), 并且一个class只会初始化一次。

双亲委派模型

这里写图片描述

Bootstrap加载器负责将存放在JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的。

双亲委派模式的工作流程:

加载class二进制流的时候,通常情况下,当前类加载器不会加载,会把加载任务委派给当前加载器的父类去加载,除了Bootstrap之外的每个加载器都是如此。所以类的加载工作总是会由Bootstrap去执行,当Bootstrap执行失败(没有找到这个类),才使用子类去加载。

非双亲委派模式
并不是所有程序都遵守上述模式,根据特殊场景需要,需要打破双亲委派的模式。如:使用 Thread.setContextClassLoader(ClassLoader cl) 去实现父类加载器请求子类加载器执行加载请求。(JNDI、 JDBC、 JCE、 JAXB和JBI等);OSGi中也为了特殊场景需要,改变了加载规则。可参
http://blog.onlycatch.com/post/Java类加载机制

发布了126 篇原创文章 · 获赞 192 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/lemon89/article/details/69856755
今日推荐