本次学习内容为圣思园张龙的深入理解jvm
目录
类的生命周期:
类的加载
例1 基本的子类父类加载情况
例2 查看常量编译的过程
情况1 静态内容
情况2 动态内容
例3 数组类型的类
例4 接口初始化
类加载器
深入解析和重要特性
类加载器介绍,和双亲委托机制
自定义类加载器
命名空间
类的卸载:
类加载器和命名空间的理解
类加载器的双亲委托模型的好处:
总结与扩展
线程上下文类加载器 context classloader
1.首先是什么是类加载器:加载类的工具,让类进入内存。
类的加载、连接与初始化
加载:查找并加载类的二进制数据。 把类的class文件加载到内存里。
连接:1.验证,确保被加载的类的正确性。
2.准备:为类的静态变量分配内存,并将其初始化为默认值。
3.解析,把类中的符号引用转换为直接引用
初始化:为类的静态变量赋予正确的初始值
类的使用与卸载
Java程序对类的使用方式分为两种: 主动、被动 使用
所有JVM实现必须在每个类或者接口被java程序“首次主动使用”时才会初始化他们。
主动使用:7种
- 创建类的实例
- 访问某个类或者接口的静态变量,或者对静态变量赋值。
- 调用类的静态方法 (助记符)
- 反射,Class.forName(“”);
- 初始化一个类的子类
- JVM启动时被标明为启动类的类。
- Jdk1.7 开始提供动态语言的支持。Java.lang.invoke.MethodHandle实例的解析结果
- 遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,加入类还没进行初始化,则马上对其进行初始化工作。其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)、以及执行静态方法的时候。
被动使用:除了以上7个,其他使用java;类都被看做被动使用,都不会导致初始化。(只不进行第三个步骤,初始化)
将类的.class文件中的二进制数据读到内存中, 将其放在运行时数据区的方法 区域内,然后内存中穿件一个java.lang.class 对象(规范并未说明Class对象位于哪里,Hotspot 虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。
-加载.class的方式
本地,网络,从zip、jar等归档文件中,从转悠的数据库中,将java源文件动态编译为.class文件。
同时我们可以通过添加jvm参数来查看类加载信息:
-XX:+TraceClassLoading 追踪类的加载信息 (虚拟机参数)
情况1 静态内容
反编译之后,发现MyParent2 str变量在MyTest2 的main方法中就加载进去了。
助记符这里不清楚没有关系,在下一届字节码中会详细介绍。
情况2 动态内容
第三个例子会输出myparent3的静态代码部分
第四个例子中,不会输出myparent4的静态代码部分
并且数组类型的类为下图中输出的部分显示。
对准备阶段和初始化阶段的
执行结果
2
1
Counter1:2
counter2:0
类的加载
- 类的加载最终产品是位于内存中的Class对象
- Class对象封装了类在方法区内的数据结构,并向Java程序员提供了访问方法区内的数据结构的接口。
- 两种类型到的类加载器:
- Java虚拟机自带的加载器
- 根类加载器 bootstrap
- 扩展类加载器 extension
- 系统(应用)类加载器 system
- 用户自定义类架子爱妻
- Java.lang.ClassLoader 的子类
- 用户可以定制类的加载方法。
- Java虚拟机自带的加载器
- 类加载器并不需要等到某个类“首次主动使用”时再加载它。
- Jvm规范运行类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到.class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误。linkageError。如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
类的验证:
- 类被加载后,就进入连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时的环境中去。
- 类的验证的内容:1.类文件的结构检查。2.语义检查。3.字节码验证。4.二进制兼容性验证。
类的初始化:
- 步骤:
1.假如这个类还没有被加载和连接,那就先进行加载和连接。2.加入类存在直接的父类,并且父类没有被初始化,那就先初始化直接父类。3.加入类中存在初始化语句,那就一次执行这些初始化语句。
- 初始化的时机:
当java虚拟机初始化一个类的时候,要求它所有的父类都已经被初始化,但这条规则不适用于接口。
·初始化一个类,并不会先初始化它所实现的接口。·在初始化一个接口时,并不会先初始化它的父接口。
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用待定接口的静态变量时,才会导致接口的初始化。
只有当程序访问的静态变量或者静态方法确实在当前类或者当前接口定义时,才可以认为是对类或接口的主动使用。
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
在父亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器,其余的类加载器有且只有一个父加载器。
如果有一个类加载器能够成功加载Test类,那么这个类加载器被称为 定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器。
对于数组类的Class对象,不对被ClassLoader创建,而是由java runtime在需要的情况下自动创建。对于一个数组类的类加载器,是通过Class.getClassLoader()返回的,返回是与数组元素的类型的加载器是一样的。如果元素类型是原生类型,数组类型没有类加载器。
第一null是根类加载器,第二是系统,第三是没有加载器。
主要是通过自定义类加载器来展自定义的基本情况和现双亲委托机制。
在工程目录之外,来加载类,并把工程目录中的类删掉。实现自定义类加载器
并且在工程目录下删除了MyTest1
输出结果为:
每个类的加载器都有自己的命名空间,命名空间有该加载器以及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。而在不同命名空间中,有可能出现。
当mysample类被加、连接和初始化后,它的生命周期就开始了。当代表mysample类的class对象不再被引用,即不可触及是,Class对象就会结束生命周期,Mysample类在方法区内的数据也会被卸载,从而结束MySample累的生命周期。一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。由jvm自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。前面已经介绍过,jvm自带的类加载器包括,根,扩展,系统类加载器。Jvm本身始终引用这些类加载器,而这些类加载器则会始终引用他们锁在家的类的Class对象,因此这些Class对象始终是可触及的。由用户自定义的类加载器所加载的类是可以被卸载的。
子加载器可以看到父加载器加载的类,但是反过来不可以。
例子,sample类里构造方法new了一个cat对象。
在工程环境中 删除mysample mycat .class 。运行如下:
删除mySmaple.class。运行如下:
Loader1加载的mysample,loader1加载mycat,但又委托给系统加载器加载mycat。
删除mycat.class 。运行如下:
因为加载mysample的类加载器,加载mycat,而mysample是系统加载器,将从工程目录下寻找。
当在mycat 里面再访问mysample,进行如下修改
再删除mySmaple.class。运行如下:
Mysample是由自定义的加载器加载,mycat是由系统加载器加载的。此问题涉及到命名空间。系统类加载器看不到自定义加载的内容,所以报错
再改造一下:
执行后不会报错
Sample是由自定义加载器,cat是系统加载器。命名空间的规定【在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。】
- 可以确保java核心库的类型安全:所有的java应用都至少会引用java.lang.object类,也就是说在运行期,java.lang.object。这个类会被加载到jvm中,如果这个加载过程是由java应用自己的类加载器所完成,那么很可能会在jvm中存在多个版本的object类,而这些类之间还是不兼容的,互不可见(命名空间发挥的作用)。 借助双亲委托机制,java核心类库中的类的加载工作都是有启动类加载器来统一完成,从而确保了java应用所使用的都是同一个版本的java核心类库,他们之间是互相兼容的。
- 可以确保java核心类库所提供的类不会被自定义的类所替代。
- 不同类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在jvm中,只需要不同的类加载器来加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在jvm内部创建了一个又一个相互隔离的java类空间,这类技术在很多框架中都得到了实际应用。
把扩展类加载器,设置为工程目录,但是不会由扩展类加载器加载。
扩展类加载器会从jar包内加载。
在运行期,一个java类是由该类的完全限定名(binary name二进制名)和用于加载该类的定义加载器(defining loader)所共同决定的。
类加载器也是一个类,那么类加载器是由谁来加载的?扩展和系统加载器都是由根加载器加载的。Jvm启动的时候就会启动。
内鉴于jvm中的启动类加载器会加载java.lang.ClassLoader以及其他java平台类。当jvm启动,一块特殊的机器码会运行,它会加载扩展类加载器与系统类加载器,这块特殊的机器码叫做启动类加载器(BootStrap)
启动类加载器并不是java类,而其他加载器则都是java类。
启动类加载器是特定于平台的机器指令,它负责开启整个加载过程。
启动类加载器还会负载加载供JRE正常运行所需的基本组件,这包括java.until与java.lang包中的类等等。
Classs.forName(String name, Boolean initialize, ClassLoader loader);
第二个指定用不用初始化
当前类加载器 Current Classloader 每个类都会使用自己的类加载器(即自身的类加载器)来去加载其他类(指的是所依赖的类),如果ClassX引用了ClassY,那么X的类加载器就会去加载Y,前提是Y没有加载
从jdk1.2开始,Thread 中的getContextClassLoader 还有set方法。如果没有通过set方法设置的话,线程将继承父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器 来加载类与资源。
线程上下文类加载器的重要性:
Connection 和Statement类都是由根加载器加载的,但是如果厂商按照这两个接口编写的驱动,加载的话,则会使用系统类加载器加载。根据双亲委托原则,父看不到子,根加载器加载的类Connection看不到具体实现的类和代码,因为加载不了。双亲委托的缺点 SPI(Service Provider Interface)场景,java提供接口,厂商提供方法。
父ClassLoader可以使用当前线程Thread.currentThread().getContextLoader()所指定的classLoader加载的类。这就改变了父ClassLoader不能使用子ClassLoader或者其他没有直接父子关系的ClassLoader加载的类的情况,改变了双亲委托模型。
线程上下文类加载器就是当前线程的Current ClassLoader
在双亲委托模型下,类加载器是由下至上的。即下层的类加载器会委托上层进行加载,但是对于SPI来说,有些接口是Java核心库所提供的,而java核心库是由启动类加载起加载的,而这些接口的实现却来自于不同的jar包。Java的启动类加载器是不会加载器其他来源的jar包,这样传统的双亲委托模型无法满足SPi的要。而通过给当前线程设置上下文类加载器,就可有设置的上线文类加载器来实现对于接口实现类的加载。
上下文加载器的三个步骤 获取 – 使用 –还原
当高层提供了统一的接口让底层去实现,同时又要在高层加载或者实例化底层的类时,就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载。
ServiceLoader可以找到所有实现Driver接口的实例,这是因为再此处保存了相关信息。
以上就是本篇文章【深入理解JVM-类加载器】的全部内容了,欢迎阅览 ! 文章地址:http://xiaoguoguo.dbeile.cn/quote/1583.html 行业 资讯 企业新闻 行情 企业黄页 同类资讯 网站地图 返回首页 多贝乐移动站 http://xiaoguoguo.dbeile.cn/mobile/ , 查看更多