win2012r2 建设网站,wordpress模板无法自定义导航,做网站想注册商标是哪一类,网站开发实训心得800类加载
类加载时机
类加载的过程
新术语
类加载器 简单的理解为将类转换为二进制流的类或接口。
数组的元素类型 数组去掉所有维度的类型。
数组的组件类型 数组去掉一个维度的类型。
基本块 按照控制流拆分的代码块。 1. 加载
加载是类加载过程的一个阶段。加载阶段主…类加载
类加载时机
类加载的过程
新术语
类加载器 简单的理解为将类转换为二进制流的类或接口。
数组的元素类型 数组去掉所有维度的类型。
数组的组件类型 数组去掉一个维度的类型。
基本块 按照控制流拆分的代码块。 1. 加载
加载是类加载过程的一个阶段。加载阶段主要完成三件事情
根据类的全限定名获取定义此类的二进制字节流。将这个二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。在内存中生成一个代表该类的java.lang.Class对象作为访问方法区中该类各种数据的外部接口。
在上述的1中没有限定此类的格式所以可以是一个class文件可以是一个jar包也可以是运行时生成等等。我们可以通过重写一个类加载器的findclass()方法或者loadClass方法来自定义字节流的获取方法。
数组与类加载器
数组类本身是由JVM在内存中直接构造的但是又与类加载器紧密联系其遵循规则如下 如果数组的组件类型是一个引用类型那么会递归的使用加载过程去加载该组件类型数组将被标识在加载该组件类型的类加载器的类名称空间上。 若组件类型是基本类型JVM会将该数组标记为与BootStrapClassLoader关联 数组类的可访问性与其组件类型的可访问性一致。
2. 验证
验证是连接的第一步器目的是确保Class文件的字节流中包含的信息完全符合《JVM规范》中的全部约束条件保证这些信息在运行时不会威胁到JVM的安全。其大致可分为四个阶段文件格式验证元数据验证字节码验证和符号引用验证。
文件格式验证
这一阶段要验证字节流是否符合Class文件格式的规范以保证输入的字节流能够正确的解析并存储到方法区内。格式上要符合一个Java类型信息的要求。只有文件格式验证通过后才能将字节流中的信息存储到方法区中所有后面的是三个验证都是基于方法区的存储结构进行的而不是字节流。
元数据验证
这个阶段要求对字节码的描述信息进行语义分析也就是保证其描述的信息符合《Java语言规范》的要求。
字节码验证
该验证的目的是通过数据流分析和控制流分析确定程序语义的合法性、合逻辑性。在元数据验证通过后该阶段对类的方法体也就是Class文件中的Code属性进行校验分析保证类的方法不会在运行时危害到JVM。
在JDK6后将尽可能多的校验辅助措施挪到javac编译器中具体的做法是在Code属性中增加了一个StackMapTable属性该属性描述了方法体所有的基本块开始时本地变量和操作栈应有的状态。在字节码验证时只需要检查StackMapTable中记录是否合法即可而不用根据程序推导这些状态的合法性。
符号引用验证
该阶段发生在JVM将符号引用转为直接引用的时候其在连接的第三阶段解析阶段才发生。是对类自身以外的各类信息进行匹配性校验比如说该类是否缺少或禁止访问它依赖的某些额外部类等若无法通过验证会排除java.lang.IncompatibleClassChangeError的子类异常java.lang.NoSuchFieldError等。
3. 准备
准备阶段是正式为被static修饰的变量类变量分配内存并设置初始值的阶段。
特别注意 此阶段的内存分配仅仅包括类变量不包括实例变量。 若类变量同时被final修饰也就是通常说的常量那么其赋值不会是基本类型的零值而是指定的值。 例如public staitc final int INIT_VALUE99,那么在准备阶段INIT_VALUE会被赋值为99而不是0这是因为被static final同时修饰是在javac编译时字段属性表中会有一个ConstantValue属性在准备阶段该变量值就会被初始化为ConstantValue属性所指定的初始值。
基本数据的零值
数据类型零值int0long0Lshort(short)0char‘\u0000’byte(byte)0booleanfalsefloat0.0fdouble0.0dreferencenull
4. 解析
解析是JVM将符号引用转换为直接引用的过程。
符号引用 以一组符号来描述所引用的目标符号可以是任何形式的字面量只要在使用时能够无歧义的定义到目标即可。符号引用与JVM的内存布局无关。
直接引用 是可以直接指向目标的指针、相对偏移量或者一个能够间接定位到目标的句柄。其与JVM内存布局直接相关。
句柄 是由系统所管理的引用标识该标识可以被系统重新定位到一个内存地址上。
解析动作主要针对类或结构、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这七类符号医用进行其对应着8种常量类型。
4.1 类或者接口的解析
假设当前的类为D要将其中未解析过的符号引用N解析为一个类或者接口C的直接引用。大概过程如下 若C不是一个数组那么JVM会将代表N的全限定类名交个D的类加载器由其去加载这个类C。若在C的加载过程中出现异常那么解析过程宣告失败。若C是一个数组并且数组的元素类型是对象那么会按照上一步的步骤去加载元素类型再由JVM生成数组对象。若1、2都没问题则检查D对C的访问权限。若没有权限则会抛出java.lang.IllegalAccessError。
4.2 字段解析
若要对一个没有经过解析的字段进行解析首先我们需要看字段表内的class_index项中的CONSTANT_class_info符号引用进行解析参考类文件结构也就是对字段对应的类或者接口的引用的解析。
假设字段对应的类或接口为C那么在解析类或接口成功后会根据《JVM规范》对字段进行搜索
若C本身包含的简单名称和字段描述符都与目标相匹配的字段则返回这个字段的直接引用搜索结束。否则若C实现了接口那么将会按照继承关系从下向上递归搜索各个接口及父接口若接口中包含了简单名称和字段描述符都与目标相匹配的字段则返回该字段的直接引用搜索结束。否则若C不是java.lang.Object的话就会按照继承关系从下向上递归搜索其父类若在父类中包含了简单名称和字段描述符与之相匹配的字段则返回该字段的直接引用搜索结束。否则查找失败抛出java.lang.NoSuchFieldError异常。若查找过程成功返回了引用则进行访问权限验证若无权限抛出java.lang.IllegalAccessError异常。
4.3 方法解析
方法解析的第一步也是对方法表内的class_index项中的CONSTANT_class_info符号引用进行解析参考类文件结构。若解析成功则会按照如下的规则来搜寻对应的方法。
Class文件格式中类的方法和接口的方法的符号引用时分开存储的所以若类的方法表中发现class_index对应的是一个接口的则抛出java.lang.IncompatibleChangeError。若1通过若类C中有简单名称和描述符都与目标匹配的则返回这个方法的直接引用搜索结束。否则在类C的父类中递归查找简单名称和描述符与目标都匹配的方法若有则返回这个方法的直接引用搜索结束。否则在类C的接口列表及他们的父接口中递归查询简单名称和描述符与目标都匹配的方法若有则表示C是一个抽象类搜索结束抛出java.lang.AbstractMethodError。否则查找失败抛出java.lang.NoSuchMethodError。若查找过程成功返回了引用则进行访问权限验证若无权限抛出java.lang.IllegalAccessError异常。
4.4 接口方法解析
基本同方法解析。若接口解析成功接下来的方法搜索规则如下
若方法表中class_index对应的是一个类java.lang.IncompatibleChangeError排除异常。否则在C中查找是否有简单名称和描述符都与目标相配的方法则返回该方法的直接引用搜索结束。否则在接口C的父类中递归查找直到java.lang.Object有简单名称和描述符都与目标相配的方法则返回该方法的直接引用搜索结束。对于3由于接口运行多继承所以存在在多个接口中都有简单名称和描述符都与目标相配的情况那么会从这些方法中返回一个并结束查找。否则查找失败抛出java.lang.NoSuchMethodError。若查找过程成功返回了引用则进行访问权限验证若无权限抛出java.lang.IllegalAccessError异常。JDK9的影响
5. 初始化
类初始化是类加载过程的最后一个阶段。在这个阶段JVM才真正开始执行类中编写的程序代码将主导权交给程序。在准备阶段已经对类变量赋了零值在这一阶段将会根据程序编码去初始化类变量和其它资源。也可以说初始化就是执行类构造器方法的过程。
接下来我们对clinit()方法做一些说明。
clinit()方法是由javac编译器自动生成的是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的编译器收集的顺序是由语句在源文件中出现的顺序觉得的静态语句块中只能访问到在静态语句块之前的变量定义在其之后的变量在该语句块中只能赋值不能访问。clinit()方法与init()方法类的构造函数不同它不需要显示的调用父类构造器JVM会保证在子类的clinit()执行前父类的clinit()已经执行完毕。可以推论出JVM中第一个被执行的clinit()方法是java.lang.Object的。由于父类的clinit()先执行所以父类的静态语句块要优先于子类的变量赋值操作。clinit()对于接口来说是非必要的。接口中不能使用静态代码块但是可能存在类变量的赋值操作因而接口也会生成clinit()方法。但是当接口的clinit()方法执行时不要求父接口的clinit()方法先执行只有当父类中定义的变量被使用时父接口才会被初始化。此外接口的实现类在初始化时也不会执行接口的clinit()方法。JVM必须保证一个类的clinit()方法在多线程环境下被正确的加锁同步。若一个类的clinit()方法中有耗时很长的操作那就可能造成阻塞。
类加载器
对于任意一个类都必须由加载它的类加载器和这个类本身一起确立其在JVM中的唯一性。
双亲委派模式
从虚拟机的视角类加载器可以分为
BootStrapClassLoader启动类加载器用C实现是JVM的一部分。其他类加载器用Java实现都继承了抽象类java.lang.ClassLoader独立存在于JVM之外。
从使用这角度可分为 BootStrapClassloader 负责加载在JAVA_HOME\lib目录或者被-Xbootclasspath参数所指定的路径中存放的并且是能够被JVM所识别的类库加载到虚拟机内存中。 Extension Class Loader 这个类是在sum.mis.Launcher$ExtClassLoader中以Java代码实现的。它负责加载JAVA_HOME\ext\目录中或者被java.ext.dirs系统变量所指定的路径中的所有类库。 Application Class Loaser这个类由sum.mis.Launcher$AppClassLoader来实现有时也被称为“系统类加载器”它用来加载用户类路径上所有的类库。
JDK9之前的Java应用都是由这三类加载器来相互配合完成加载。通常这些类加载器按照下图的协作关系来完成加载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ztTqTNW1-1617897204791)(https://gimg2.baidu.com/image_search/srchttp%3A%2F%2Fimg4.mukewang.com%2F5bdf01aa0001a43210380303.jpgreferhttp%3A%2F%2Fimg4.mukewang.comapp2002sizef9999,10000qa80n0g0nfmtjpeg?sec1620482748td3df08dd58dfb0a1299f9f0207afff4c)]
这样的模型被称为双亲委派模型其工作过程如下
若一个类加载器收到了类加载请求它首先不会自己去加载而是将这个请求委托给父类加载器去完成每一层次的类加载器都是如此。因此所有的加载请求最终都应该传送到最顶层的BootStrapClassloader ,只有当父加载器反馈自己无法完成这个加载请求及在它的搜索范围类没有找到所需的类子需求才会去尝试自己完成加载。
注意双亲委派中的父加载器不是继承关系中的父子关系而是通过组合关系来复用父加载器的代码。
破坏双亲委派
在上一点中我们提到通常情况下加载是按照双亲委派模型执行意味着存在这其它方法也就是双亲委派模型被破坏。按历史反正可以分为下面三种情况
第一次
由于双亲委派模型是JDK1.2引入的ClassLoader是在第一个版本就存在了的并且加载的核心代码在loadClass中可参考《JVM》P284所以为了兼容用户已经自定义类加载器的情况双亲委派在实现中做出了妥协在loadClass方法中加了一个protected修饰的findClass方法并引导用户使用findClass。
第二次
第二次破坏是基于双亲委派的模型自身的缺陷双亲委派很好的解决了基础类型一致性的问题但是对于基础类型需要回调用户的代码双亲委派无能为力。这个时候引入线程上下文类加载器。待深入研究
第三次
这次破坏基于对代码热替换模块热部署的追求。例如OSGi。待深入研究 参考资料
《深入理解Java虚拟机》
Tomcat类加载器破坏双亲委派
从JDBC看“破坏”双亲委派模型
真正理解线程上下文类加载器
服务发现机制