java虚拟机的类加载机制
引言
我们写的代码是放在.java文件中,经过编译器编译后,转成.class文件。Class文件是一串二进制流,它可以被各平台的虚拟机所接受,实现跨平台。
虚拟机将描述类的数据从class文件加载到内存,并对数据进行校验、解析、初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。Java中,类型的加载、连接和初始化过程都是在程序运行期间完成的,比如要编写一个面向接口的程序,就可以等到运行时再指定具体实现类。
类的生命周期
类的生命周期包括七个阶段:加载、验证、准备、解析、初始化、使用、卸载。其中,验证、准备、解析三个阶段合称为连接阶段。如图:
其中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,而有些情况下解析过程可以在初始化之后进行。这些阶段通常都是相互交叉的混合式进行的,通常会在一个阶段执行的过程中调用、激活另一个阶段。
什么时候进行类加载的第一个阶段呢?Java虚拟机没有约束,而是交给虚拟机自己把握。但是,对于初始化阶段,虚拟机规范严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然在这之前进行)。(下面是5个必须进行初始化类的地方,也就是什么时候开始初始化,对于初始化的内容,在后面具体过程中在介绍。)
1)遇到new、getstatic、putstatic、invokestatic四条字节码指令时,如果没有对类进行初始化,就要立马对其进行初始化。生成这四条指令最常见的Java场景:new 关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译时把结果放入常量池的静态字段除外)、调用一个静态方法。
2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化则需要先触发其初始化。
3)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机先初始化这个主类。
5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。
注意:有且只有这五种情况才会触发类的初始化,这5个情况中的行为称为对一个类进行主动引用。除此之外的所有其他引用类的方式都不会初始化类,称为被动引用,下面列举三个不会触发初始化的情况。
1)子类继承父类,通过子类调用父类的静态字段,只会导致父类初始化,不会导致子类初始化。
2)通过数组定义来引用类,不会触发此类的初始化。SuperClass[] a=new SuperClass[10];
3)调用静态常量不会初始化类。public static final String s="hello world";常量在编译阶段就会存入调用类的常量池,本质上并没有直接引用到定义常量的类。
类加载的过程
1.加载
在加载阶段,虚拟机完成三件事:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的类的静态存储结构(如静态属性、静态字段)转化为方法区的运行时数据结构(也就是按照方法区的格式存进方法区)。
3)在内存中生成一个代表这个类的java.lang.Class对象,用来作为访问方法区中这些类型数据的入口。
对于上面的第一条,虚拟机没有指明二进制流从哪获取、怎么获取。所以,这里Java开发人员就玩出许多花样来:
a)从zip包中读取,最终成为JAR、EAR、WAR的基础。
b)从网络获取,如Applet。
c)运行时计算生成,动态代理。
d)从其他文件生成,jsp应用,从jsp生成对应的Class类。
。。。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中了,然后在内存中实例化一个java.lang.Class类的对象(没有明确要放在堆中,hotspot放在方法区中),作为程序访问方法区中这些类型数据的外部接口。
2.验证
验证阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证这一阶段很复杂,但从整体上看,验证阶段大致会完成下面四个阶段的检验:文件格式验证、元数据验证、字节码验证、符号引用验证。
1)文件格式验证
第一个阶段就是要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。这里验证的内容也挺多的,此处省略好多字。
这个阶段的验证是基于二进制字节流进行的,只有通过这个阶段后,字节流才会进入到内存的方法区中,后面的验证都是基于方法区的存储结构进行的,不在直接操作字节流。
2)元数据验证
这一阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java虚拟机语言规范的要求,比如:这个类是否含有父类,父类是否继承了不允许被继承的类(final),如果该类不是抽象类,是否实现了其父类或接口中的要求实现的所有方法。。。等等。
3)字节码验证
主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段是对类的方法体进行校验,保证这些方法在运行时不会危害虚拟机。
4)符号引用验证
符号引用验证发生在将符号引用转换为直接引用的时候(解析阶段发生)。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
3.准备
准备阶段正式为类变量(static变量)分配内存并设置类变量初始值。这些类变量所使用的内存都在方法区中分配。这里只是static变量,实例变量将会在对象实例化的时候随着对象一起进入到Java堆中。通常情况下,将类变量初始化为零值(根据不同基本数据类型会不一样),但是对于特殊情况:如果该类字段被final修饰,这时就会将其设置为自身的数据。
4.解析
解析阶段虚拟机将常量池中的符号引用替换为直接引用。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载在内存中了。
直接引用:直接引用可以是直接指向目标的指针、相对偏移或一个能间接定位到目标的句柄。直接引用和虚拟机的内存布局有关,如果有了直接引用,那引用的目标必定已经在内存中存在了 。
对于同一个符号引用进行多次解析请求是很常见的,除了invokedynamic指令外,虚拟机实现可以对第一次解析的结果进行缓存,从而避免解析动作重复进行。如果一个符号引用之前被成功解析过,那么后续的引用解析请求应当一直成功;反之收到相同异常。
5.初始化
在准备阶段已经对类变量进行过一次系统要求的初始化,而在初始化阶段,则根据程序员的意愿去初始化类变量和其它资源,或者换一个角度表达:初始化阶段是执行类构造器<clinit>()方法的过程。下面是该放的一些特点:
1)<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并而成的,编译器的收集顺序有语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的静态变量,定义在它之后的变量,在前面的语句块可以赋值,但是不能访问。
2)<clinit>)方法与类的构造函数不同,它不需要显示的调用父类构造器,虚拟机会保证在子类的<clinit>()调用之前,父类的<clinit>已经被调用了,所以,虚拟机中第一个被执行的<clinit>肯定是Object的
3)父类中定义的静态代码块要优先于子类的变量赋值。
4)<clinit>()方法对于一个类或接口来说不是必须的,如果没有静态代码块,可以不为此类生成该方法。
5)虚拟机会保证每个类的《clinit》()方法在多线程下被正确的加锁、同步,如果多线程同时去初始化一个类,那么只有一个线程会执行这个<clinit>()方法,其他线程阻塞等待。
6)接口中不能使用静态代码块,但是接口中也有变量的赋值操作(只能常量)。
类加载器
虚拟机设计团队将类加载阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,然后再看代表类的Class对象的equals()方法,。。等方法。
双亲委派模型
从Java开发人员的角度来看,类加载器可以划分为以下三种系统提供的类加载器。
1)启动类加载器:这个类加载器负责将放在<JAVA_HOME>\lib目录下的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。开发者不能直接使用启动类加载器
2)扩展类加载器:负责加载<JAVA_HOME>\lib\ext下,或者被java.ext.dirs系统变量所指定的路径中的类库,开发者可以直接使用扩展类加载器。
3)应用程序类加载器(也称为系统类加载器):它负责加载用户路径(ClassPath)上所指的类库,开发者可以直接使用这个加载器。如果应用程序没有自己定义类加载器,一般默认使用这个类加载器。
双亲委派模型(parents delegation Model)的层次关系r如图:
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承实现,而都是使用组合。
双亲委派模型的工作过程:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器,每一层的类加载器都是如此,因此所有的类加载器最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(它的搜索范围内没有找到所需要的类),子类加载器才会尝试去加载这个类。
使用双亲委派模型来组织类加载器之间的关系,一个好处就是Java类随着加载器一起具备了优先级的层级关系。例如java.lang.Object类,无论哪个类加载器需要加载这个类,最终都是为派给启动类加载器进行加载,所以Object类在程序的任意一个类加载器环境中都是同一个类。
java虚拟机的类加载机制的更多相关文章
- java 虚拟机的类加载机制
Java 虚拟机的类加载机制 关于类加载机制: 虚拟机把描述类的数据从Class 文件加载到内存,并对数据进行效验.转换解析和初始化,最终 形成可以被虚拟机直接使用的Java 类型,就是虚拟机的类 ...
- 深入理解java虚拟机【类加载机制】
Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程. 在加载阶段,java虚拟机需要完成以下 ...
- Java虚拟机:类加载机制详解
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 大家知道,我们的Java程序被编译器编译成class文件,在class文件中描述的各种信息,最终都需要加载到虚拟机内存才能运行和使用,那么 ...
- 深入理解Java虚拟机(类加载机制)
文章首发于微信公众号:BaronTalk 上一篇文章我们介绍了「类文件结构」,这一篇我们来看看虚拟机是如何加载类的. 我们的源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行.虚拟机 ...
- 【进阶之路】深入理解Java虚拟机的类加载机制(长文)
我们在参加面试的时候,经常被问到一些关于类加载机制的问题,也都会在面试之前准备的时候背好答案,但是我们是否有去深入了解什么是类加载机制呢?这段时间因为一些事情在家看了些书,这次就和大家分享一些关于Ja ...
- 深入理解Java虚拟机之类加载机制篇
概述 虚拟机把描述类的数据从 Class 文件加载到内存中,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,就是虚拟机的类加载机制. 在Java语言里面,类型的 ...
- Java虚拟机之类加载机制
⑴背景 Java虚拟机把Class文件加载到内存中,并对数据进行校验,转换解析,和初始化,最终形成被虚拟机直接使用的Java类型,这就是类加载机制. ⑵Jvm加载Class文件机制原理 类的生命周 ...
- 深入理解java虚拟机(三)-----类加载机制
类加载机制jvm把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被jvm直接使用的java类型.在java中,类型的加载.连接和初始化都是在程序运行期间完成的 ...
- 《java虚拟机》----类加载机制
No1: 实现语言无关性的基础仍然是虚拟机和字节码存储格式,虚拟机只与Class文件这种特定的二进制文件格式所关联,并不关心Class的来源是何种语言. No2: Class文件是一组以8位字节为基础 ...
随机推荐
- 基于V4L2摄像头采集图片程序设计
#ifndef __COMMON_H #define __COMMON_H //该头文件定义的是摄像头在屏幕上显示的宽度和高度 #include<stdio.h> #include< ...
- Android实现登录小demo
安卓,在小编实习之前的那段岁月里面,小编都没有玩儿过,如果说玩儿过,那就是安卓手机了,咳咳,敲登录的时候有种特别久违的熟悉,这种熟悉的感觉就和当时敲机房收费系统一样,那叫一个艰难啊,不过小编相信,在小 ...
- input事件--->按键事件的基本实现
本程序基于TINY4412开发板,程序已经验证过,完全正确: 那么,如何来写这样的一个驱动程序呢? 1.分配一个input_dev结构体 2.设置 3.注册 4.硬件相关的代码,比如中断,定时器,休眠 ...
- Java中怎么简单的使用正则表达式?
对于正则表达式,我通常的认识就是通过一些陌生的奇怪的符号就可以完成很复杂事件的好帮手!实际上正则表达式确实是这方面的好助手,接下来让我们一起认识一下Java中怎么使用正则表达式吧. 初见Pattern ...
- android打包方法超过65k错误
近日,Android Developers在Google+上宣布了新的Multidex支持库,为方法总数超过65K的Android应用提供了官方支持. 如果你是一名幸运的Android应用开发者,正在 ...
- 修改GDAL库支持RPC像方改正模型
最近在做基于RPC的像方改正模型,方便对数据进行测试,修改了GDAL库中的RPC纠正模型,使之可以支持RPC像方改正参数. 下面是RPC模型的公式,rn,cn为归一化之后的图像行列号坐标,PLH为归一 ...
- 关于React Native 火热的话题,从入门到原理
本文授权转载,作者:bestswifter(简书) React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原 ...
- 我眼中的Linux设备树(三 属性)
三 属性(property)device_type = "memory"就是一个属性,等号前边是属性,后边是值.节点是一个逻辑上相对独立的实体,属性是用来描述节点特性的,根据需要一 ...
- Android轶事之View要去大保健?View大小自己决定?
-"爹,我要吃糖" -"好哒儿子" -"爹,我要吃包包" - "好哒儿子" - "爹,我要吃串串" ...
- 关于oracle表名区分大小写的问题
oracle不是区分大小写的,是建表的时候是没有去掉双引号. CREATE TABLE TableName(id number); //虽然写的时候是有大写和小写,但是在数据库里面是不区分的. ...