《深入理解java虚拟机》第七章读书笔记——虚拟机类加载机制
一丶虚拟机类加载机制是什么
java虚拟机将描述类的数据从class文件加载到内存
,并对数据进行校验,转换解析和初始化
,最终形成可用被虚拟机直接使用的java类型。
二丶类加载时机
1.什么时候会触发虚拟机的类类加载昵?
- 遇到
new(使用new关键字实例化对象)
,getstatic(读取一个类的静态非final字段)
,putstatic(设置一个类的非final静态字段)
,或invokestatic(调用一个类型的静态方法)
这些字节码指令的时候,如果类没有经过初始化,那么需要先触发其初始化阶段。 - 使用java反射手段,对类型进行反射调用的时候,如果类型没有进行初始化,那么将先触发其初始化。
- 当都初始化类的时候,如果其父类没有进行初始化,那么则需要先触发其父类的初始化。
- 当虚拟机启动的时候,虚拟机会先初始化用户指定的主类
- 当使用
MethodHandler
实例,并解析为REF_getstatic
,REF_putstatic
,REF_invokestatic
,REF_newinvokeSpecial
四种类型方法句柄,并且这个方法句柄对应的类没有经过初始化,那么先进行类的初始化 - 如果一个接口定义了default方法,其实现类发生了初始化,那该接口要在其之前进行初始化
2.不会触发类加载,但是有趣的几种案例
2.1子类引用父类静态属性
public class SuperClass {
public static int staticValue =1;
public static final int finalValue = 2;
static {
System.out.println("super");
}
}
public class SubClass extends SuperClass{
static {
System.out.println("sub");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(SubClass.staticValue);
}
}
Main类的main方法执行,只会输出super
,说明并没有触发子类的初始化。这是因为:对于静态字段,只有直接定义这个字段的类才会初始化
2.2 使用静态final字段,并不会触发类的初始化
public class Main {
public static void main(String[] args) {
System.out.println(SuperClass.finalValue);
}
}
使用静态final字段并不会触发类的初始化。这是由于编译阶段通过常量传播优化,已经将此常量的值直接存储于SuperClass的常量池中,对常量的使用,都被转化为SuperClass对自身常量池的引用了,Main的Class文件并没有SuperClass类的符号引用入口,编译之后Main类和SuperClass并没有任何联系了。
这是Idea target中Main类,反编译后的内容,可用看到并没有对SuperClass的引用。
2.3 使用类的数组类型,并不会触发类的初始化
public class Main {
public static void main(String[] args) {
SubClass[] subClasses = new SubClass[10];
}
}
上面所示代码,并不会触发SubClass的初始化,因为SubClass[].class
这种类型,是由虚拟机自动生成,并继承自Object类型,创建动作由newarry
字节码指令触发。
三丶类加载过程
1.加载
通过类的全限定类名,获取此类的二进制字节流、
- 从zip压缩包中获取——jar,war 格式的基础
- 从网络中获取 —— web applet 引用
- 运行时计算生成——动态代理结束,使用java动态代理Proxy生成代理对象的时候,就是生成
*$Proxy
代理类的二进制字节流
加载阶段可用使用java虚拟机里内置的
引导类加载器
完成,也可以自定义类加载,重写findClass 或者loadClass方法
,可用自定义如何获取类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在堆中生成一个代表此类的Class对象,作为方法区这个类的各种数据数据的入口
经过加载阶段,java虚拟机外部的二进制字节流就按照虚拟机设定的格式存储在方法区中了(图中的运行时数据结构),类型数据存储后,会在java堆中生成Class对象
,作为方法区类型数据的访问接口
注意这时还不能生成该类的实例,还需要进行验证,准备,解析,初始化
2.验证
验证时为了保证class文件字节流中包含的信息符合虚拟机规范,保证这些信息被当作代码运行后,不会危害虚拟机的安全。
2.1 文件格式校验
验证字节流符合class文件格式规范,并且可用被当前版本的虚拟机处理。
2.2 元数据校验
对字节码描述信息进行语义校验,保证其描述的信息符合虚拟机规范
- 当前类是否由父类,除了Object外所有类都需要由父类
- 这个类是否继承了被final修饰的类
- 如果类不是抽象类,那么是否实现了必须实现的方法
- .....
2.3 字节码验证
通过数据流分析,和控制流分析,确定程序语义是合法的,符合逻辑的。
2.4 符号引用验证
符号引用验证,发生在解析阶段
,就是验证对类滋生外的各类信息进行匹配型校验。检查该类是否缺少或者被禁止访问它依赖的某些外部类,方法,字段等。
3.准备
准备阶段是正式为类中i党员的变量(静态属性)分配内存和设置初始值的阶段。
如果是static final
类型的字段在此阶段会进行值的设置(static final int a= 1,在这个阶段会设置a的值为1)
但是static属性只会设置初值(static int a =1,在这个阶段值为0)
4.解析
解析阶段负责将常量池中符号引用,替换为直接引用。
符号引用:
用一组符号描述所引用的目标,可用是任何形式的字面量,可以没有歧义的定位到目标。
直接引用:
指向目标的指针,相对偏移,或者能简介定位到目标的句柄。
对于invokedynamic指令,并不会缓存第一次解析的结果,invokedynamic又称为动态调用限定符,动态意味着等程序运行到这条指令的时候才会去解析。其余触发解析的指令都是静态的,可用在完成加载阶段后进行解析。
5.初始化阶段
准备阶段已经进行赋零值的操作,初始化阶段就是执行类的构造器<cinit>()
,此方法由java编译器自动生成,会自动收集类中所有类变量的赋值动作和静态代码块,并合并产生,收集的顺序由源文件中的顺序决定。
- 虚拟机保证在调用子类的
<cinit>()
方法之前,父类的<cinit>()
已经执行完毕。 - 父类的
<cinit>()
先于子类执行 - 如果一个类没有静态代码块,也没有对类静态变量的赋值操作,那么编译器可用不生成
<cinit>()
方法 - 接口中不能使用静态代码块,但仍然可有由变量的初始化操作,执行接口的
<cinit>()
不需要先执行其父类的<cinit>()
,只有当父类的静态变量被使用的时候才会调用父类的<cinit>()
。接口实现类初始化的时候也不会调用接口的<cinit>()
- 虚拟机保证一个类的
<cinit>()
方法在多线程环境下,会正确的加锁同步(为什么说静态代码块的单例是线程安全的
)。
四丶类加载器
类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例
,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)
。
类加载器在java程序中的作用不仅仅是类的加载阶段,还会作用于Class对象的equals,isAssignableFrom,以及isInstance()和instanceof关键字
,只有两个类是由同一个类加载器加载的前提下,才能“相等”(满足前面说的equals,isAssignableFrom等)。不同的类加载器加载相同的全限定类名,可能在java虚拟机中存在多个独立的类。
启动类加载器
启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将
<JAVA_HOME>/lib
路径下的核心类库或-Xbootclasspath
参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。扩展类加载器
扩展类加载器是
ExtClassLoader
类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext
目录下或者由系统变量-Djava.ext.dir
指定位路径中的类库,开发者可以直接使用标准扩展类加载器。应用程序类加载器
AppClassLoader
。它负责加载系统类路径java -classpath
或-D java.class.path
指定路径下的类库,也就是我们经常用到的classpath
路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()
方法可以获取到该类加载器。
五丶双亲委派模型
1.何为双亲委派
如果一个类加载器收到类加载请求,首先不会自己尝试加载,而是把此请求交给父类加载器进行加载,因此最终所有的类都将交给启动类加载器进行加载,只有当父加载器反馈自己无法完成这个加载请求(搜索范围内不存在目标类)子加载器才会去自己加载
可用看到父类抛出ClassNotFoundException后,也会尝试自己去加载。
2.双亲委派的好处
双亲委派保证了,所有类的记载,首先交给父类加载器进行,保证相同的类只会存在一个(都交给了父,父如果加载不了,那么子,最终相同的类肯定由相同的类加载器进行加载)。如果打破双亲委派,那么我们可用自己实现java.lang.Object,系统中会出现多个Object类,java最基础的行为都无法得到保证。
3.打破双亲委派
JNDI
现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath
下的JNDI接口提供者(SPI,Service Provider Interface
)的代码,但启动类加载器不可能去加载ClassPath
下的类。但是有了线程上下文类加载器就好办了,JNDI服务使用线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。
Tomcat
中可以部署多个web
项目,为了保证每个web
项目互相独立,所以不能都由AppClassLoader
加载,所以自定义了类加载器WebappClassLoader
- 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在
resourceEntries
这个数据结构中),如果已经加载即返回,否则 继续下一步。 - 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,返回继续。
- 前两步均没加载到目标类,那么web应用的类加载器将自行加载,如果加载到则返回,否则继续下一步。
- 最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。
- 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在
《深入理解java虚拟机》第七章读书笔记——虚拟机类加载机制的更多相关文章
- <深入理解计算机系统>第七章读书笔记
第七章读书笔记 链接 链接:将各种代码和数据部分收集起来并组合成为一个单一文件的过程.(这个文件可被加载或拷贝到存储器并执行) 链接可以执行于编译,加载或运行时. 静态链接: 两个主要任务: 1 符号 ...
- 2013337朱荟潼 Linux&深入理解计算机系统第七章读书笔记——链接
第七章--链接 0.总结 链接编译时可以采用静态链接或动态链接. 连接器主要任务:符号解析和重定位. 多个目标文件可定义相同的符号,可以被连接到一个单独的静态库. 链接器可以生成部分链接的可执行文件 ...
- 《Linux内核设计与实现》第七章读书笔记
第七章.中断和中断处理 7.1中断 中断使得硬件得以发出通知给处理器.中断随时可以产生,内核随时可能因为新来到的中断而被打断. 不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标志.操作系统给 ...
- 20135202闫佳歆--week 7 深入理解计算机系统第七章--读书笔记
参见上学期的学习笔记: http://www.cnblogs.com/20135202yjx/p/4836058.html
- linux第七章读书笔记
Vim编辑器 Vim 仅仅通过键盘来在插入和执行命令等多种模式之间切换.这使得Vim可以不用进行菜单或者鼠标操作,并且最小化组合键的操作,对文字录入员或者程序员可以大大增强速度和效率. CHAPTER ...
- Android深度探索--HAL与驱动开发----第七章读书笔记
首先创建led驱动的设备文件,可以使用cdev_init,register_chrdev_region,cdev_add等建立主设备号的设备文件.步骤如下: 1使用cdev_init初始化cdev 2 ...
- 以C语言为例的程序性能优化 --《深入理解计算机系统》第五章读书笔记
其实大多数的编译器本身就能提供一些简单的优化,比如gcc就能通过使用 -O2 或者 -O3 的选项来优化程序.但编译器的优化始终也是有限,因为它必须小心翼翼保证优化过程不对程序的功能有改动.故而程序员 ...
- 《深入理解Java虚拟机》第三章读书笔记(二)——HotSpot垃圾回收算法实现(OopMap,安全点安全区域,卡表,写屏障,三色标记算法)
系列文章目录和关于我 前面<深入理解Java虚拟机>第三章读书笔记(一)--垃圾回收算法我们学习了垃圾回收算法理论知识,下面我们关注下HotSpot垃圾回收算法的实现,分为以下几部分 对象 ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- “全栈2019”Java多线程第七章:等待线程死亡join()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
随机推荐
- Day18.2:对象创建的内存分析图解
对象创建的内存分析 我们从两块最常用的内存空间对对象创建进行内存分析 堆内存:存放的是对象的具体信息:在程序之中堆内存空间的开辟是通过new完成的 栈内存:存放的是对象的地址信息,即通过地址找到堆内存 ...
- 【element】中el-row如何使内容垂直居中
查阅官方文档,只需要在el-row中设置属性align为middle即可
- Google Chrome(谷歌浏览器)安装使用
谷歌浏览器官网https://www.google.cn/chrome/ Chrome是由Google开发的一款简单便捷的网页浏览工具.谷歌浏览器(Google Chrome)可以提帮助你快速.安全的 ...
- 【软考-中级-其他】03、NoSQL和云计算
其他 NoSQL概述 分类 文档存储数据库:MongoDB 采用BSON格式完成存储数据和网络数据交换 BSON格式:JSON的二进制编码格式 逻辑结构包括:数据库.集合(相当于关系数据库的表).文档 ...
- 【Java SE】Day05数组
一.数组的定义和访问 1.初始化 动态new int[10];--默认值 静态new int[]{1,2,3};,省略为{1,2,3}; 2.访问 长度arr.length属性(数组的属性) 打印数组 ...
- 【每日一题】【字符串与数字互转】【去除空格】【大数处理】2021年12月12日-8. 字符串转换整数 (atoi)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数). 函数 myAtoi(string s) 的算法如下: ...
- 【转载】【Word】项目编号应用样式后出现黑框的解决方案
本文中的宏代码来自: https://www.zhihu.com/question/38985919 Word使用项目编号后,出现黑框,如下图所示: 网上大多数解决方案在重新打开文档后,依然还是有黑框 ...
- SQL Server下7种“数据分页”方案,全网最全
数据分页往往有三种常用方案. 第一种,把数据库中存放的相关数据,全部读入PHP/Java/C#代码/内存,再由代码对其进行分页操作(速度慢,简易性高). 第二种,直接在数据库中对相关数据进行分页操作, ...
- [Untiy]贪吃蛇大作战(一)——开始界面
前言: 刚学unity没多久吧(大概1个月多点),这是我自己做的除官网之外的第一个游戏demo,中间存在很多不足的地方,但是还是希望可以给需要的人提供一些思路和帮助,有问题的小伙伴可以找我一起探讨一起 ...
- centos7.6安装本地yum源
centos7.6安装本地yum源 前言:文章内容可能会因环境不同而有所差异,所谓集思广益说不定灵感就来了呢; 文章初衷旨在交流学习.记录个人成长,如果能帮助到您,那就点个赞噢. 环境说明: 1.本实 ...