首页 / JAVA / java虚拟机的类加载机制
java虚拟机的类加载机制
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了java虚拟机的类加载机制,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7500字,纯文字阅读大概需要11分钟。
内容图文
![java虚拟机的类加载机制](/upload/InfoBanner/zyjiaocheng/783/005a480d93374031a91b4d323520c754.jpg)
一、概述
????虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和说出实话,最终形成可以被虚拟机直接使用的java类型,这就是 虚拟机的类加载机制。
????与其他语言不同,java不是在编译期间进行连接的,java中类型的加载、连接和初始化都是在 程序运行期间 进行的。
二、类加载的时机
????类有一个生命周期,类的生命周期如下图所示:
????加载、验证、准备、初始化、卸载 这5个阶段开始的顺序是确定的,但是解析阶段不一定在图中这个位置。解析在某些情况下在初始化之后才开始(java语言的动态绑定)。
????这7个阶段的相对顺序只是开启的先后顺序,并不一定是前一个执行结束后下一个才能开始,如:验证过程开始得比准备和解析都早,但是当解析过程开始的时候,验证过程可能还未结束,这些过程都是交叉进行的。
????当且仅当在下列5种情况下时,如果类没有进行过初始化,就必须立即对类进行初始化:
(1) 遇到new、getstatic、putstatic或invokestatic这4个字节码指令的时候,在java中对应的常见场景是,(使用new关键字实例化对象的时候、读取或者设置一个类的静态字段、调用一个类的静态方法的时候)
(2) 使用java.lang.reflect包的方法对类进行反射调用的时候
(3) 初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要触发其父类的初始化。
(4) 当虚拟机启动时,会最先初始化用户指定的包含main方法的类
(5) 使用kdk 1.p7的动态语言支持时,如果一个MethodHandle实力最后的解析结果REF_getstatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发初始化。
????当且仅当上述5种情况下,是对类的主动调用,在其他情况下都是对类的被动调用。
????接口的加载与类不同的地方在于前面的5种情况中的第3种,即接口在初始化的时候并不要求父接口都完成了初始化,只有真正用到父接口的时候(如引用父接口中的常量)才会初始化。
3、类加载的过程
3.1 加载
????在加载阶段,虚拟机主要完成以下3件事情:
(1) 通过一个类的全限定名获取定义此类的二进制字节流。
(2) 将这个字节流代表的静态数据结构转换为方法区的运行时数据结构
(3) 在内存中生成一个代表该类的java.lang.Class对象(Class对象比较特殊,是放在方法区中的),作为方法区这个类的各种数据的访问入口。
**[注]**第(1)中的获取二进制字节流不一定非要从Class文件中获取,还可以从下列途径获取:
(1) 从ZIP包中读取
(2) 从网络中读取,如Applet
(3) 运行时计算生成,如动态代理技术,java.lang.reflect.Proxy中就用了ProxyGenerator.generateProxyClass来为特定接口生成“¥Proxy”的代理类的二进制字节流
(4) 由其他文件生成,JSP中就是用JSP文件生成对应的Class类。
(5) 从数据库中读取,如有些中间件服务器可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
加载阶段既可以使用系统提供的引导类加载器来完成,也可以通过用户自定义的类加载器来完成。不过自定义的类加载器只在加载阶段有效,其余阶段还是由虚拟机主导和控制。
3.2 验证
????验证是连接阶段的第一步,这个阶段的目的是为了确保读取的Class二进制字节流信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身。
????验证阶段分为4段:
(1) 文件格式验证
????这个阶段会验证二进制字节流是否以魔数开头、主次版本号是否在虚拟机范围内、常量有无不被支持的类型等。
????只有通过了格式验证,字节流才会被存入内存中的方法区,后面的三步验证都是基于方法区的数据结构进行的,不再操作字节流。
(2) 元数据验证
????对字节码描述的信息进行语义分析,确保其描述的信息符合java语言规范要求,如:如果一个类不是抽象类,是否实现了父类或者接口中要求实现的方法。
(3) 字节码验证
????通过数据流和控制流分析,确定程序语义是否合法、符合逻辑。这个阶段主要对类的方法体进行校验分析。如:保证跳转指令不会跳转到方法体之外的字节码指令上,保证方法体中的类型转换是有效的。
(4) 符号引用验证
????最后一个验证过程将与解析阶段同时进行,它和解析阶段配合,将符号引用转化为直接引用。它对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
????符号引用验证的目的就是确保解析操作能正常进行,在这个阶段可能会抛出NoSuchError和NoSuchMethodError等错误。
**[注]**验证阶段比较重要,但是不一定必要,如果所运行的代码已经被反复验证过,在实施阶段就可以考虑关闭大部分类的验证措施,以缩短虚拟机类加载的时间。
3.3 准备
????准备阶段是正是为类变量分配内存并设置类变量初始值的阶段,变量所使用的内存都将在方法区中进行分配 。(只分配类变量,不分配实例变量,实例变量将在对象实例化的时候随着对象一起分配在java堆中)
????并且给类变量分配的值都是0值,真正给变量赋值的动作将在初始化阶段才会执行。但是如果是常量(static final),那么在准备阶段就会将代码中的初值赋值给该常量。
3.4 解析
????解析阶段将 常量池中的符号引用替换为直接引用。
(1) 符号引用
????符号引用通过一组符号(如Class文件中的CONSTANT_Class_info)来引用目标(这里的目标通常也是字符串形式,并不是内存中的具体的东西),符号引用与虚拟机的内存布局无关,它引用的目标也不一定加载到了内存。
(2) 直接引用
????直接引用可以是直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用直接指向虚拟机中的一个有效的类、方法或者字段。直接引用和虚拟机的内存布局有关,同一个符号引用在不同的虚拟机下翻译得到的直接引用通常不同。有了直接引用,那么引用的目标必定已经在内存中。
????在刚加载好一个类的时候,Class文件里的常量池和每个方法的字节码(Code属性)会被基本原样的拷贝到内存里先放着,也就是说仍然处于使用“符号引用”的状态;直到真的要被使用到的时候才会被解析(resolve)为直接引用。
3.5 初始化
????到了初始化阶段,才真正开始 执行 类中定义的java程序代码。在准备阶段,变量已经赋过一次零值,在初始化阶段,则是根据程序员的主观意愿去初始化类变量和其他资源。
????初始化阶段是执行类构造器<clinit()>的过程。
<clinit()>方法的特点:
(1) 该方法是编译器收集类中的所有 类变量的赋值动作 和 静态代码块 合并而成。
(2) <clinit()> 方法与<init()>不同,它不需要显式地调用父类构造器,虚拟机会保证子类<clinit()>执行之前,其父类的<clinit()>已经执行完毕。
(3) <clinit()>方法不是必须的,如果一个类没有静态代码块或者静态变量的赋值操作,那么编译器可以不为这个类生成<clinit()>方法
(4) 虚拟机会保证一个类的<clinit()>方法在多线程环境下被正确的加锁、同步,多个线程同时去初始化一个类的时候,只有一个线程能执行这个类的<clinit()>方法。如果这个类中的<clinit()>方法有耗时长的操作,就可能造成多个线程阻塞。
4、类加载器
4.1 类与类加载器
????可以自定义自己的类加载器。但是自定义的类加载器和默认类加载器加载同一个二进制文件之后,产生的类是不同的(即使名字一样)。
4.2双亲委派模型
从java虚拟机角度来看,存在两种类加载器:
(1) 启动类加载器
????虚拟机自带的类加载器,用C++语言实现
(2) 用户自定义的加载器
????用java语言实现,继承自抽象类ClassLoader。
从开发人员角度来看,java默认的加载器可以分得更细:
(1) 启动类加载器
????将<JAVA_HOME>\lib目录中的类库加载到虚拟机内存中
(2) 扩展类加载器
????将<JAVA_HOME>\lib\ext目录中的类库加载到虚拟机内存中
(3) 应用程序类加载器
????该类加载器是ClassLoader的getSystemClassLoader()方法的返回值,所以也成为系统类加载器,负责加载Classpath上指定的类库。如果用户没有自定义自己的类加载器,就会默认使用该加载器。
????java应用程序都是在这3种类加载器的相互配合下进行加载的,如果有必要还可以加上自己的类加载器。
它们相互配合的关系称为类加载器的双亲委派模型:
????双亲委派模型的工作过程如下:
如果一个类加载器收到了类加载的请求,它首先不会自己加载这个类,而是把请求委派给父类加载器,所有的类加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法加载这个类的时候,子加载器才会尝试自己去加载。
双亲委派模型的好处:
(1)为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
(2) 避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类。
自定义类的应用场景:
(1) 加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。
(2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。
(3)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。
内容总结
以上是互联网集市为您收集整理的java虚拟机的类加载机制全部内容,希望文章能够帮你解决java虚拟机的类加载机制所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。