【Java杂货铺】JVM#虚拟机加载机制
代码编译的结果从本地机器码变为字节码,是储存格式发展的一小步,却是编程语言发展的一大步——《深入理解Java虚拟机》
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转化解析和初始化,最终形成了可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类型的加载、连接和初始化都是在程序运行期间完成的,虽说加大了运行时期的开销,但是大大增加了Java的灵活度,方便动态加载和连接。Java不仅可以从Class文件获取属于,也可以从其他地方例如网络中直接获取二进制流数据,这极大提高了Java的延展性。
时机
类的生命周期
类从开始加载到卸载一共经过了七个过程,如下图。
其中验证、准备、解析统称为连接。另外,加载、验证、准备、初始化和卸载这5个过程只是开始要按照顺序,可以同时执行,不用等待上一个过程结束之后才执行。例如,我在9点开始准备,9点10分开始初始化,9点20准备结束。
初始化时机
有且只有下面五种情况,才可以称为“初始化”:
- 遇到 new、getstatic、putstatic、invokestatic这4个字节码指令的时候,发现类没有进行初始化,才进行初始化。其中关于new的理解,除了生成普通的类实例,当调用类的静态方法的时候也会触发初始化。
- 使用java.lang.reflect包内的方法对类进行反射调用的时候。
- 当一个要初始化的类发现父类还没有初始化的时候,首先需要初始化父类。
- 当虚拟机启动的时候初始化要执行的主类(main()方法所在的类)。
- 使用JDK1.7+版本的动态语言支持时,发现类没有初始化需要初始化之。
除此之外,所有引用类的方式都不会触发初始化,仅被称为被动引用。
开个小差,在一个类的静态代码块中,如果某变量提前被被赋值,就可以被使用;如果某变量之后才赋值的,在静态代码块中使用就会报错。但是无论何时赋值,只要声明了,在静态代码块中再赋值是被允许的。看下这个例子:
public class Test{
static{
i=0;//给变量赋值可以正常编译通过
System.out.print(i);//这句编译器会提示"非法向前引用"
}
static int i=1;
}
对于接口来说,有且仅有前三种情况才会被称为初始化。另外,对于接口,不需要满足提前让父接口初始化,除非你有用到父接口的时候。
过程
逐步看下加载、验证、准备、解析和初始化这5个过程。
加载
加载过程需要完成以下三个事情:
- 通过类的全限定名来获取此类的二进制字节流。
- 将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构。
- 再内存种生成一个代表这个类的java.lang.Class对象,这种对象有别于其他普通对象,是在方法区的。
对于非数组的类,加载可以通过虚拟提供的类加载器,也可以通过一用户自定义的加载器。对于数组类,数组本身不是通过加载器加载的,而是通过Java虚拟机直接创建的,数组中的元素是通过加载器创建的。
加载过程结束后,内存中就会得到一个该类的java.lang.Class对象,为后续铺垫。
验证
在加载开始的同时,验证择机开启。验证是为了确保Class文件的字节流种包含的信息符合上章讲的规格,不会危害虚拟机本身。这个阶段是否严谨,直接决定了Java虚拟机是否能承受恶意代码的攻击,从执行性能的角度讲,验证阶段的工作量在虚拟机类加载子系统中又占了相当大的一部分。
文件格式验证
首先需要验证是否符合Class文件格式的规范,比如魔数(咖啡宝贝)是否存在,主次版本号是否可以被当前虚拟机运行、常量类型的tag标志等等。这个阶段的验证时基于二进制字节流进行的,只有通过了这个阶段的验证后,字节流才会进入内存的方法区中进行储存,后面三个验证阶段全是基于方法区的储存结构进行的,不再直接进行字节流操作。
元数据验证
此过程包含验证是否有父类、父类是否允许被继承啊,各种修饰符是否冲突啊等等。
字节码验证
主要目的时通过数据流和控制流分析确定程序语义是合法的、符合逻辑的。此过程保证任意时刻的操作数栈的数据类型与指令代码序列都能配合工作,保证跳转指令不会跳转到方法体以外的字节码指令上,保证类型转化是正常的,保证父类和子类之间的字段不冲突等等。
由于数据流验证非常复杂,为了减缓消耗的时间,自JDK1.6开始,方法体的Code属性的属性表中增加了一项为“StackMapTable”的属性,这项属性描述了方法体中所有的基本块。在字节码验证期间,就不需要根据程序推到这些状态的合法性,只需要检验StackMapTable属性中的记录是否合法即可。大大节省了字节码验证的时间。
符号引用验证
此阶段发生在虚拟机将符号引用转化成直接引用的时候,这个转化动作将在连接的第三个阶段解析的时候发生。需要验证是否可以通过字符串的全限定名找到这个类,指定的类中是否符合方法的字段描述符以及简单名称所描述的方法和字段,类、方法、字段的访问性等等。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。此时给静态变量设置初始值是零值,并不是代码中设置的具体值,具体值还需要在putstatic指令执行时才会初始代码中设置的值。除非此static变量被final修饰了们就会在此时直接设置代码中的值。
解析
解析阶段是虚拟机将常量池内的符号引用替换成直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存分布无关,引用的目标并不一定已经加载到内存中。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。
除了invokedynamic指令以外,虚拟机实现可以对第一次解析的结果进行缓存。invokedynamic指令是可动态语言支持相关的指令,所以无法做到缓存。
初始化
类初始化时类加载过程的最后一步。前面的操作除了自定义的类加载器之外,都是虚拟机主导的操作,初始化阶段,开始整整执行类中定义的Java代码了。
初始化阶段时执行类构造器<client>()方法的过程。<client>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。<client>()方法不需要显示的构造父类的构造函数,已经自己构造好了,并且父类的静态代码块是先于子类的静态代码块的。并且<client>()方法执行时带锁的,不同线程执行这个方法可能会出现线程阻塞的现象。
类加载器
虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到Java虚拟机外部去实现了,实现这个动作的代码块叫做类加载器。
类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性。如果说某个类相等,那么这两个类一定是在同一个类加载器下加载完成的。这里的相等可以使用Class的equals方法、isAssignableFrom()方法、isInstance()方法验证,也可以使用instanceof关键字做对象所属关系的判断。例如全限定名都是com.pjjlt.MyTest。一个用虚拟机自己的类加载器加载,一个用用户自定义的类加载器加载,那么这两个类就不相等,分别产生的对象实例用instanceof关键字只能作用域自己的类上才会是true。
双亲委派机制
那么问题来了,我要用自定义的类加载器加载一个Object放到内存中,那岂不是整个Java的基础功能全废了。其实不然,新建的Object类也会和原生的那个Object类是被一样对待的。这就涉及了双亲委派机制。
对于虚拟机的角度来说,只有虚拟机的类加载器和用户自定义的类记载器。对于用户来说有启动类加载器(Bootstrap ClassLoader)、拓展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)这么几种,而且他们是一种组合关系来复用父加载器。
双亲委派机制工作原理:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有它反馈自己无法加载的时候,才会交给子加载器加载。
这也解释了为什么你写的Object加载器创造出来的类和原生的是同一款了,因为人家就没有被你自己写的类加载器所加载,而是某父层的加载器加载了。
【Java杂货铺】JVM#虚拟机加载机制的更多相关文章
- Java高级之虚拟机加载机制
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1.0版本:2016-05-21 SubClass!! 执行结果说明一个问题:子类调用父类变量的时候 ...
- jvm系列(一):java类的加载机制
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
- 深入java虚拟机学习 -- 类的加载机制
当看到"类的加载机制",肯定很多人都在想我平时也不接触啊,工作中无非就是写代码,不会了可以百度,至于类,jvm是怎么加载的我一点也不需要关心.在我刚开始工作的时候也觉得这些底层的内 ...
- jvm系列一、java类的加载机制
一.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...
- Java虚拟机(三):Java 类的加载机制
1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...
- JVM:java类的加载机制
原文连接:https://www.cnblogs.com/ityouknow/p/5603287.html 类加载机制的奥妙. 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读 ...
- JVM(1):Java 类的加载机制
原文出处: 纯洁的微笑 java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang. ...
- JVM:Java 类的加载机制
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制. 类的生命周期 类从被加载到虚拟机内 ...
- 深入java虚拟机学习 -- 类的加载机制(续)
昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...
随机推荐
- jsp页面引入不了js路径没错
最近搞开发,发现有个jsp页面引入不了js:很是神奇,路径什么的都没问题,同事的浏览器可以加载该js,发现放到其他的文件夹下可以加载该js:当时没研究出来,任务紧就没研究了. 最近闲下来了,有去研究, ...
- spring_mybatis :整合
第一步:导入相关架包(使用maven构建项目) 在pom.xml文件中导入相关依赖 1.Junit测试架包 <dependency> <groupId>junit</gr ...
- k8s安装helm
1.客户端安装 A.直接在github上下载二进制文件进行解压,下载地址:https://github.com/kubernetes/helm/releases B.将解压出来的二进制文件helm 拷 ...
- SpringCloud学习之大纲总略(大纲篇)
微服务架构的概念,现在对于大家应该都不陌生,无论使用 Apache Dubbo.还是 Spring Cloud,都可以去尝试微服务,把复杂而庞大的业务系统拆分成一些更小粒度且独立部署的 Rest 服务 ...
- ZOJ 2301/HDU 1199 线段树+离散化
给这个题目跪了两天了,想吐简直 发现自己离散化没学好 包括前一个离散化的题目,实际上是错了,我看了sha崽的博客后才知道,POJ那题简直数据弱爆了,本来随便一组就能让我WA掉的,原因在于离散化的时候, ...
- sonarqube linux安装总结,集成jenkins
第一条建议,安装sonarqube首先看好版本号,不同版本号的安装配置可能不同,如果你想走捷径,看官网对应发布的安装使用教程.https://www.sonarqube.org/downloads/ ...
- SQL基础教程(第2版)第1章 数据库和SQL
● 数据库有很多种类,本书将介绍如何使用专门的 SQL语言来操作关系数据库.● 关系数据库通过关系数据库管理系统(RDBMS)进行管理. 根据 SQL 语句的内容返回的数据同样必须是二维表的形式,这也 ...
- VNC viewer 无法打开oracle 11g图形界面方案
VNC viewer 无法打开oracle 11g图形界面方案 1.检查交换空间失败 检查交换空间:可用的交换空间为35MB,所需的交换空间为150MB.未通过 创建swapfile: root权限下 ...
- A - Shortest path of the king (棋盘)
The king is left alone on the chessboard. In spite of this loneliness, he doesn't lose heart, becaus ...
- 和我一起从0学算法(C语言版)(一)
第一章 排序 第一节 简化版桶排法 友情提示:此文章分享给所有小白,大牛请绕路! 生活中很多地方需要使用排序,价格的由低到高.距离的由远及近等,都是排序问题的体现.如果排序量较少,依靠个人能力很容易实 ...