关于jvm的类加载过程,jvmclass加载机制
00-1010前言1。JVM 2的组成。类别加载1。正在加载2。链接3。正在初始化3。类加载器引导类加载器(启动类加载器)扩展类加载器应用程序类加载器4。父母授权机制。课堂的主动/被动使用
目录
女士们先生们,你们好,我很羡慕。在这一节,我们进入jvm的学习。我们知道,jvm是一个java虚拟机,java代码的执行与jvm密切相关。接下来,我们依次介绍一下。首先,本节介绍jvm中的类加载部分。
前言
jvm可以分为四个部分。
1.类别载入器(类别载入器)
2.运行时数据区(运行时数据区)
3.执行引擎(执行引擎)
4.本地库接口(本机接口)
那么一个程序在jvm中的运行过程是怎样的呢?
Java代码首先被编译成字节码文件(类文件),由不同操作系统上的JVM加载和解释。在这个过程中,首先需要类加载器加载类文件,然后进行字节码验证。验证完成并通过后,由JVM解释器翻译成机器码,交付给操作系统执行。
在程序执行之前,java代码应该被转换成字节码(ClassLoader)。jvm首先需要通过一定的方式将字节码加载到内存中的运行时数据区,字节码文件是jvm的一套指令集规范,不能直接交给底层操作系统执行。因此,需要一个特定的命令解析器执行引擎来将字节码翻译成底层的系统指令,这些指令将由CPU执行。在这个过程中,需要调用其他语言的Native接口来实现整个程序的功能,这是这四个主要组件的职责和作用。
jvm的总体结构如下
在这一点上,初级班同学可能有点傻,不过不用担心,上图中的所有组件都会一一讲解。
00-1010从上面jvm的运行过程可以看出,类加载就是读取类文件的过程。这期间需要类加载器,类加载器只负责加载,如何执行由执行引擎决定。
类加载分为以下模块
类加载器将类文件加载到jvm中,这称为DNA元数据模板。jvm通过模板创建实例,类加载器在这个过程中充当信使。
那么类加载的过程是怎样的呢?
可以看出有三个过程:加载、链接、初始化。
00-1010 1.通过类名(地址)获取这个类的二进制字节流。
2.把这个字节流表示的静态存储结构转换成方法区(元空间)的运行时结构。
3.在内存中生成一个表示该类的java.lang.Class对象,作为该类各种数据的访问门户。
顾名思义,加载就是将类中的信息加载到jvm中。
00-1010环节分为:验证、准备、分析三个流程。
(1)验证
检查加载的类是否具有正确的内部结构,并且与其他类协调一致。
验证文件格式是否一致。3360类文件在文件开头有一个特定的文件标识符(字节码文件都是以CA FE BA BE标识符开头);主版本号和次版本号是否在当前java虚拟机的接收范围内?
元数据验证:对字节码描述的信息进行语义分析,保证其描述的信息符合java语言规范的要求,比如这个类是否有父类;是否继承和浏览不允许继承的类(最终修饰类)
验证过程主要看是否符合java语言的规范。
(2)准备工作
准备:准备阶段负责为类的静态属性分配内存,设置默认初始值(int为0)。
此过程不包含用final修饰的静态常数(static constants),在编译时初始化。
//准备阶段的值为0 public static int value=123//准备阶段值为123 public static final int value=123;(3)分析
用直接引用替换类的二进制数据中的符号引用(符号引用是类文件的逻辑符号,直接引用指向方法区域中的一个地址)
p> 将符号引用替换成直接引用, 这句话怎么理解呢 ? 这里来举个例子
public void method1(){ method2();}
在 方法1 中调用 方法2 , 我们这样来写代码的时候, 这就只是符号引用 , 而当这段程序被加载进 jvm解析的时候 符号引用就会变成直接引用, 也就是指明此处真正的引用地址
3. 初始化
在谈类的初始化过程之前, 先来考虑 , 类什么时候会被初始化?
1 )创建类的实例,也就是 new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName())
5)初始化一个类的子类(会首先初始化子类的父类)
初始化类的过程也是为类中成员赋值的过程 , 在链接过程中的准备过程中被static修饰的变量是赋了默认值(int型为0), 而在初始化过程中才会赋予我们赋的值
我们常说 , 用 static 修饰的变量, 方法 , 代码块是跟类直接打交道的 , 我们说加载类的时候, 使用static修饰的成员也会被加载 , 此过程也是在类的初始化中完成
那么在初始化过程中, 赋值顺序是怎样的呢?
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
顺序是:父类 static –> 子类 static –> 父类构造方法- -> 子类构造方法
下面代码 num 的值变化
3. 类加载器
从开发人员的角度上来讲, 类加载器可分为3类 : 引导类加载器(启动类加载器), 扩展类加载器 , 应用程序类加载器
引导类加载器(启动类加载器)
这个类加载器使用 C/C++语言实现,嵌套在 JVM 内部.它用来加载 java 核心类库.
并不继承于java.lang.ClassLoader , 没有父加载器 , 负责加载扩展类加载器和应用程序类加载器 , 并为它们指定父类加载器
ClassLoader 类,它是一个抽象类,其后所有的类加载器都继承自 ClassLoader (不包括启动类加载器)
引导类加载器作为顶级的类加载器, 非java语言实现 , 所以和java中其他类加载器也不存在继承关系等
扩展类加载器
Java 语言编写的,由sun.misc.Launcher$ExtClassLoader 实现. 派生于 ClassLoader 类.
负责从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的 jar 放在此目录下,也会自动由扩展类加载器加载
应用程序类加载器
Java 语言编写的,由 sun.misc.Launcher$AppClassLoader 实现. 派生于 ClassLoader 类. 加载我们自己定义的类,用于加载用户类路径(classpath)上所有的类. 该类加载器是程序中默认的类加载器.
我们自己写的类是由应用程序类加载器加载的, 类加载器结构示例如下
//获取应用程序类加载器 sun.misc.Launcher$AppClassLoader@18b4aac2ClassLoader classLoader = ClassLoader.getSystemClassLoader();System.out.println(classLoader);//获得父类加载器 sun.misc.Launcher$ExtClassLoader@74a14482System.out.println(classLoader.getParent());//扩展类加载器上一级是引导类加载器,不是java实现,为nullSystem.out.println(classLoader.getParent().getParent());//拿到String类的类加载器,结果为null//可见,String类为引导类加载器加载ClassLoader classLoader1 = String.class.getClassLoader();System.out.println(classLoader1);
另外还有一种叫做用户自定义类加载器 , 例如 tomcat
4. 双亲委派机制
什么是双亲委派机制呢?
Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要该类时才会 将它的class 文件加载到内存中生成 class 对象.而且加载某个类的 class 文件 时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委 派模式.
.
就是说呢, 如果类加载器接收到了加载请求, 并不会去立即加载这个类, 而是把请求交给它的上一级加载器去加载 , 上一级没有则继续往上找 , 直到顶级的类加载器(引导类加载器)也无法加载时, 开始往下找 , 如果有一级加载成功则返回, 最终加载器都无法加载时, 就会抛出ClassNotFoundException异常
那么为什么要这样去做呢? 试想, 我们自己创建一个java.lang.String类
package java.lang;public class String { public String(){ System.out.println("自己的String"); }}
建立一个测试类
public class TestString { public static void main(String[] args) { new String(); }}
试想 , "自己的String" 这句话会被输出吗 ? 答案肯定是不会
因为加载类的时候会先往上走, 此时走到了引导类加载器, 引导类加载器发现此类没有被加载,并且自己可以加载, 那么java.lang.String 就会被加载了, 此时就会直接返回
那么双亲委派机制出现的原因就显而易见了
安全,可避免用户自己编写的类动态替换 Java 的核心类,如 java.lang.String , 避免全限定命名的类重复加载(使用了 findLoadClass()判断当前类是否已加载)
5. 类的主动/被动使用
JVM 规定,每个类或者接口被首次主动使用时才对其进行初始化,有主动使用,自然就有被动使用.那么什么时候类被主动使用呢?
通过new关键字被导致类的初始化,这是大家经常使用的初始化一个类的方式,他肯定会导致类的加载并且初始化访问类的静态变量,包括读取和更新访问类的静态方法对某个类进行反射操作,会导致类的初始化初始化子类会导致父类的的初始化执行该类的 main 函数除了上面的几种主动使用其余就是被动使用了
1.引用该类的静态常量,注意是常量,不会导致初始化,但是也有意外,这里的常量是指已经指定字面量的常量,对于那些需要一些计算才能得出结果的常量就会导致初始化,比如:
public final static int NUMBER = 5 ; //不会导致类初始化,被动使用 public final static int RANDOM = new Random().nextInt() ; //会导致类的初始化,主动使用
2.构造某个类的数组时不会导致该类的初始化
Student[] students = new Student[10]
主动使用和被动使用的区别在于类是否会被初始化.
结语
到此关于 jvm 类加载这一章就说完了 , 感谢您的阅读 , 后续将会进行 jvm 中运行时数据区的讲解 , 感谢您的支持 ,谢谢 !!!
到此这篇关于详细分析JVM类加载机制的文章就介绍到这了,更多相关JVM类加载内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。