我们知道我们编写的java代码,会经过编译器编译成字节码(class文件),再把字节码文件装载到JVM中,最后映射到各个内存区域中,我们的程序就可以在内存中运行了。那么问题来了,这些字节码文件是怎么装载到JVM中去的呢。中间经过了哪些步骤?我们常说的双亲派模式又是怎么回事?本文就来说说这些问题。

类加载器的流程

1、加载

   加载是类加载的第一步。它首先通过class文件的路径读取到二进制流,并解析二进制流将里面的元数据(类型、常量等)载入到方法区(方法区上文说了就是存放这些元数据的),在java堆中生成对应的java.lang.Class对象(java堆存放实例对象)。

2、连接

  连接过程分为3步:验证、准备、解析

  2.1、验证

      验证的主要目的就是判断class文件的合法性,比如class文件一定是以0xCAFEBABE开头的。另外对版本号也会做校验,例如使用jdk1.8编译后的class文件要在jdk1.6虚拟机上运行,因为版本问题就会验证不通过。除此之外还会对元数据、字节码进行验证等等。

  2.2、准备

    准备过程就是分配内存,给类的一些字段设置初始值,例如:

    public static int a = 1;

    这段这段代码在准备阶段a的值就会被初始化为0,只有在后面类初始化阶段才会被设置为1.

    但是对于static final(常量),在准备阶段就会被设置成指定的值,例如:

    public static final int a=1;

     这段代码在准备阶段a的值就是1了。

  2.3、解析

    解析过程就是将符号引用替换为直接引用,例如某个类基础java.lang.object,原来的符号引用记录的是"java.lang.object"这个符号,凭借这个符号并不能找到java.lang.object这个对象在哪里,而直接引用就是要找到java.lang.object所在的内存地址,建立直接引用关系,这样就方便查询到具体对象了。

3、初始化

  

这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。

初始化过程,主要包括执行类构造方法、static变了赋值语句,static{}语句块,需要注意的是如果一个子类进行初始化,那么它会先初始化其父类,保证父类在子类之前被初始化。所以其实在java中初始化一个类,那么必然会先初始化java.lang.object,因为所有的java类都继承java.lang.object。换句话说,java.lang.object类是java所有类的父类。

类加载器

  类加载器ClassLoader,它是一个抽象类,ClassLoader的具体实例负责把java字节码读取到JVM当中,ClassLoader还可以定制以满足不同字节流的加载方式。ClassLoader负责整个类装载流程中的“加载”阶段。

  ClassLoader的重要方法:

  public Class<?> loadClass(String name) throws ClassNotFoundException :载入并返回一个类
  protected final Class<?> defineClass(byte[] b, int off, int len):定义一个类,该方法不公开被调用
  protected Class<?> findClass(String name) throws ClassNotFoundException:查找类,loadClass的回调方法
  protected final Class<?> findLoadedClass(String name):查找已经加载的类

  系统中的ClassLoader

  BootStrap Classloader (启动ClassLoader)

  Extension ClassLoader (扩展ClassLoader)

  App ClassLoader(应用 ClassLoader)

  Custom ClassLoader(自定义ClassLoader)

每个ClassLoader都有另外一个ClassLoader作为父ClassLoader,BootStrap Classloader除外,它没有父Classloader。

ClassLoader加载机制如下:

自下向上检查类是否被加载,一般情况下,首先从App ClassLoader中调用findLoadedClass方法查看是否已经加载,如果没有加载,则会交给父类,Extension ClassLoader去查看是否加载,还没加载,则再调用其父类,BootstrapClassLoader查看是否已经加载,如果仍然没有,自顶向下尝试加载类,那么从 Bootstrap ClassLoader到 App ClassLoader依次尝试加载。

值得注意的是即使两个类来源于相同的class文件,如果使用不同的类加载器加载,加载后的对象是完全不同的,这个不同反应在对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的判定结果。

双亲模式: 

protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name); //通过名称来查找已经被加载的类
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}

} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name); //如果这个通过名称没有查找到已经加载的类,则通过名称去查找这个类
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

从代码上可以看出,首先查看这个类是否被加载,如果没有则调用父类的loadClass方法,直到BootstrapClassLoader(没有父类),我们把这个过程叫做双亲模式。

双亲模式的问题:

顶层ClassLoader,无法加载底层ClassLoader的类

Java框架(rt.jar)如何加载应用的类?

比如:javax.xml.parsers包中定义了xml解析的类接口
Service Provider Interface SPI 位于rt.jar 
即接口在启动ClassLoader中。
而SPI的实现类,在AppLoader。

这样就无法用BootstrapClassLoader去加载SPI的实现类。

解决方法:

JDK中提供了一个方法:

Thread. setContextClassLoader()

用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题;
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例。

双亲模式是默认的模式,但不是必须这么做;
Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent;
OSGi的ClassLoader形成网状结构,根据需要自由加载Class。

JVM知识(二):类加载器原理的更多相关文章

  1. JVM学习--(六)类加载器原理

    我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...

  2. JVM的艺术—类加载器篇(二)

    分享是价值的传递,喜欢就点个赞 引言 今天我们继续来深入的剖析类加载器的内容.上节课我们讲了类加载器的基本内容,没看过的小伙伴请加关注.今天我们继续. 什么是定义类加载器和初始化类加载器? 定义类加载 ...

  3. JVM的艺术—类加载器篇(三)

    JVM的艺术-类加载器篇(三) 引言 今天我们继续来深入的剖析类加载器的内容.上篇文章我们讲解了类加载器的双亲委托模型.全盘委托机制.以及类加载器双亲委托模型的优点.缺点等内容,没看过的小伙伴请加关注 ...

  4. Java虚拟机JVM学习05 类加载器的父委托机制

    Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...

  5. 菜鸟脱壳之脱壳的基础知识(二) ——DUMP的原理

    菜鸟脱壳之脱壳的基础知识(二)——DUMP的原理当外壳的执行完毕后,会跳到原来的程序的入口点,即Entry Point,也可以称作OEP!当一般加密强度不是很大的壳,会在壳的末尾有一个大的跨段,跳向O ...

  6. 深入理解JVM一类加载器原理

    我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...

  7. JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用

    一.回顾一下jdk自带的类加载器: 1.java虚拟机自带的加载器     根类加载器(Bootstrap,c++实现)     扩展类加载器(Extension,java实现)     应用类加载器 ...

  8. 深入理解JVM(六)——类加载器原理

    我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...

  9. JVM性能优化--类加载器,手动实现类的热加载

    一.类加载的机制的层次结构 每个编写的".java"拓展名类文件都存储着需要执行的程序逻辑,这些".java"文件经过Java编译器编译成拓展名为". ...

随机推荐

  1. EJB3 阶段总结+一个EJB3案例 (2)

    这篇博文接着上一篇博文的EJB案例. 在上一篇博文中,将程序的架构基本给描述出来了,EJB模块分为5层. 1)DB层,即数据库层 在则一部分,我使用的数据库为mysql.在EJB程序中,访问数据库是通 ...

  2. Karatsuba乘法--实现大数相乘

    Karatsuba乘法 Karatsuba乘法是一种快速乘法.此算法在1960年由Anatolii Alexeevitch Karatsuba 提出,并于1962年得以发表.此算法主要用于两个大数相乘 ...

  3. CS231n学习笔记-图像分类笔记(下篇)

    原文地址:智能单元 K-Nearest Neighbor分类器 大家可能注意到了,为什么只用最相似的一张图片的标签来作为测试图像的标签呢?这不是很奇怪吗!是的,使用K-Nearest Neighbor ...

  4. Android Studio修改字体大小

    android studio提供的主题是不能修改字体和字体大小的,如果要修改大小就的另存一份自定义的主题了. 1.启动Android Studio,菜单"File"-"S ...

  5. SPSS学习系列之SPSS Text Analytics是什么?

    不多说,直接上干货! IBM® SPSS® Text Analytics 是一个IBM® SPSS® Modeler 完全集成内插式插件,它采用了先进语言技术和Natural Language Pro ...

  6. 探秘varian:优雅的发布部署程序

    上一篇文章<记一次诡异的故障排查经历>中有介绍到我们的部署程序varian,文章发布后有小伙伴对varian很感兴趣,今天就简单的介绍一下我们的varian,揭开她神秘的面纱~ 什么是va ...

  7. springcloud-05-ribbon中不使用eureka

    ribbon在有eureka的情况下, 可以不使用eureka, 挺简单, 直接上代码 application.xml server: port: spring: # 设置eureka中注册的名称, ...

  8. WPF 进程间传递参数

    WPF 进程间传递参数          在软件开发中有时需要在一个软件中启动另一个软件,这时用Process.Start(“软件路径”)可以启动另一个软件.如果在这个过程中还需要传递一些参数给新启动 ...

  9. JavaScript 常用的小功能集合

    1. 得到当前用户使用的浏览器的内核版本 function getExplorer(){ var browser = ""; var explorer = window.navig ...

  10. 【转】外国朋友出的js题目,你能对几道

    原文地址 http://perfectionkills.com/javascript-quiz/ 中文地址加解释:by Aaron:http://www.cnblogs.com/aaronjs/p/3 ...