类加载器

一、类加载

JVMclass字节码 文件加载到内存中, 并将这些静态数据转换成 方法区 中的运行时数据结构,在堆中 生成一个代表这个类的java.lang.Class 对象 ,作为方法区类数据的访问入口。

二、类加载过程

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括: 加载、链接( 验证、准备、解析 )、初始化、使用 卸载 七个阶段。它们开始的顺序如下图所示:

类加载器

1、加载

加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:

①从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。

②从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。

③通过网络加载class文件。

④把一个Java源文件动态编译,并执行加载。

类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

2、链接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。

2.1 验证

验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。

其主要包括四种验证, 文件格式验证,元数据验证,字节码验证,符号引用验证

四种验证做进一步说明:

文件格式验证 :主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。

元数据验证 :对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。

字节码验证 :最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。

符号引用验证 :主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。

2.2 准备

类准备阶段负责为类的静态变量分配内存,并设置默认初始值。

2.3 解析

将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

3、初始化

初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

3.1发生时机

概括得说,类初始化是【懒惰的】

  • main 方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName new 会导致初始化

3.2不会导致类初始化的情况

  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 类对象.class 不会触发初始化
  • 创建该类的数组不会触发初始化

三、类加载器

使用 C++ 实现,负责加载存放在 JDK/jre/lib (JDK 代表 JDK 的安装目录,下同)下,或被 -Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的 java.* 开头的类均被 Bootstrap ClassLoader 加载)。 启动类加载器是无法被 Java 程序直接引用的。

类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

1、引导类加载器

负责加载JRE核心类库,jre包下rt.jar,charsets.jar等,C++语言编写,无法直接访问

package com.zn;

public class ClassTest {
    public static void main(String[] args) {
        //虚拟机内置的三个类加载器
        //1.根类加载器(没有父加载器)
        ClassLoader classLoader = Object.class.getClassLoader();
        System.out.println("[跟类加载器]object类 的类加载器是:"+classLoader);
    }
}

控制台效果:

类加载器

2、扩展类加载器ExtClassLoader

负责加载JRE扩展目录ext中的jar包

package com.zn;

import sun.net.spi.nameservice.dns.DNSNameService;

public class ClassTest {
    public static void main(String[] args) {
        

        //2.扩展类加载器(扩展类加载器的父加载器是跟类加载器)
        ClassLoader classLoader1 = DNSNameService.class.getClassLoader();
        System.out.println("[扩展类加载器]DNSNameService的类加载器是:"+classLoader1);
    }
}

控制台效果:

类加载器

3、系统类加载器AppClassLoader

负责加载classPath路径下的类包

package com.zn;

import sun.net.spi.nameservice.dns.DNSNameService;

public class ClassTest {
    public static void main(String[] args) {

        //3.系统类加载器(系统类加载器的父加载器是扩展类加载器)
        //自己写的类默认走系统类加载器
        ClassLoader classLoader2 = ClassLoader1.class.getClassLoader();
        System.out.println("[系统类加载器]ClassLoader1的类加载器为:"+classLoader2);
    }
}

控制台效果:

类加载器

4、自定义类记载器

用户可以自己定义类加载器来加载类。所有的类加载器都要继承  java.lang.ClassLoader 类并重写  findClass(String name) 方法。用户自定义类加载器默认父加载器是 应用程序加载器

四、类加载机制

1、全盘负责委托机制

当进行类加载的时候,如果手动指定了ClassLoader,那么该类所依赖和引用的类也由这个类加载器进行加载

User->UserParent

指定User使用特定的类加载器,那么跟User类有依赖和引用关系的类也用这个类加载器进行加载

2、双亲委派机制

2.1 工作原理

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即 先委托父类加载器寻找目标类,如果父类加载器无法进行类的加载则子类加载器自身处理

2.2 图解

类加载器

2.3 沙箱安全机制

自定义的String.class不会被加载,这样可以防止核心API库被随意篡改

package java.lang;

public class String {
    public static void main(String[] args) {
        System.out.println("用户自定义的类");
    }
}

控制台效果:

类加载器

2.4 避免类重复加载

当附加在其加载了该类时,就没有必要子类加载器也进行加载

2.5 如何破坏双亲委派机制

①重写ClassLoad类中的loadClass方法,指定加载哪一个类

②手动调用系统类加载器Thread.currentThread().getContextClassLoader();

③重写findClass

五、监控类加载过程

在当前启动类当中加入-verbose:class参数,启动则可以看到整个类加载的过程

类加载器

代码展示:

package com.zn;

import sun.net.spi.nameservice.dns.DNSNameService;

public class ClassTest {
    public static void main(String[] args) {
        //虚拟机内置的三个类加载器
        //1.根类加载器(没有父加载器)
        ClassLoader classLoader = Object.class.getClassLoader();
        System.out.println("[跟类加载器]object类 的类加载器是:"+classLoader);


        //2.扩展类加载器(扩展类加载器的父加载器是跟类加载器)
        ClassLoader classLoader1 = DNSNameService.class.getClassLoader();
        System.out.println("[扩展类加载器]DNSNameService的类加载器是:"+classLoader1);


        //3.系统类加载器(系统类加载器的父加载器是扩展类加载器)
        //自己写的类默认走系统类加载器
        ClassLoader classLoader2 = ClassLoader1.class.getClassLoader();
        System.out.println("[系统类加载器]ClassLoader1的类加载器为:"+classLoader2);
    }
}

控制台效果:

类加载器

类加载器

六、热部署

时时检测类,如果类发生更改则自动进行重新编译,编译之后重新加载该类,提高开发的效率,修改代码后无需重启应用

原文 

http://www.cnblogs.com/Zzzzn/p/12465847.html

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 类加载器

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址