白话类加载

无论你看哪个版本JVM书籍,类加载是绕不开的开篇第一课,然而我们对其理解往往受限于JVM繁复的概念,而无法真正消化,本文力求图文结合,用大白话让读者真正理解JVM类加载阶段。
JVM类加载的阶段可以分为:加载、连接、初始化。标准的类加载阶段其实包括了5个步骤,其中连接又分为验证、准备、解析。


类加载阶段

我们一步步来理解:当我们的Java源文件(.java)文件被编译器编译成.class的字节码文件后,这个字节码文件还是存在于我们的硬盘上,我们可以在项目结构中看到这些编译后的class文件:


字节码文件

加载

java虚拟机要操作它,第一步当然是将硬盘上的字节码文件放到内存当中,这个过程就是类加载过程的第一步加载(在刚学习JVM的时候,往往把类加载过程和这个加载阶段混淆,客观来说,我也觉得这两个加载取名不够好)。这里有几点需要明确!

  • .class文件加载到内存中什么区域?
    系统内存会分配给JVM一块专门的工作内存,我们称之为运行时数据区,这个区域对于JVM的学习来说尤其重要,这里先针对问题简单介绍,后面会详细解释各部分的功能,在运行时数据区中有一块区域称之为方法区或者元空间,经过第一步加载,类的数据就进入了这个内存部分:

    类加载进入方法区

  • 怎么理解.class文件加载到内存当中?
    通过图示我们可以看到,方法区中存放的是class A的相关信息,具体包括:常量、静态变量、方法信息等等。这里我们再进一步:


    方法区中存放的类信息

    当类被加载到方法区之后,可以看到,一个类被解构到两个部分,可以看出,除了类中声明的常量进入了方法区中的常量池以外,class的其他信息以class标记,存放在方法区中。这就说清楚了一个问题,当我们在new一个对象的时候,JVM怎么知道这个对象长什么模样?答案就在这里,因为在方法区记录着这个类的所有基本信息!

  • 除此之外还有什么结果产生
    其实类在加载这个阶段,除了将类数据载入到方法区,并转化成方法区运行时数据结构以外,还有一个重要产物:在堆内存中生产一个代表这个类的java.lang.Class对象(注意这个对象是类的对象,而不是类的实例对象),作为方法区这类各种数据的访问入口。怎么理解这句话呢:如果你了解反射机制,应该知道我们可以通过反射的方式,根据类的定义创建对象实例,那么问题来了,我们通过Class.forName(全域名)这种方式如何获取到一个类的基本呢信息呢,前面我们说了,类的基本信息存放在方法区中,对于new一个对象实例,类的数据信息是从这里来的。而反射获取类信息的方式就是通过在加载阶段,堆内存中生成的java.lang.Class对象来进行获取。

说到这里我们了解了JVM会将编译好的字节码文件加载到虚拟机允许时环境的方法区当中,这些信息会从字节码文件格式转换成方法区运行时数据格式,最重要的是JVM会根据这个类的信息在堆内存当中创建一个类对象

到此为止加载的结果已经清楚了。但还有两个问题是需要解决的:1.类加载的时机,2.类加载的过程。我们先来看第一个问题:

1. 类加载的时机:

Java虚拟机规范对类加载的时机没有明确规定,但肯定是发生在运行时当中。看一些文章会将类的加载时机和类的初始化时机混淆,这里强调一下,类的加载最终目的是将class文件加载到方法区并在堆中创建对应的class对象。而类的初始化是给静态变量赋上正确的值,它发生在加载、连接之后,所以不是一回事。类的加载与类的初始化不同,它不需要等到该类被首次主动使用(后面会解释)时才去加载,JVM运行类加载器在预料到某个类要被使用时就提前加载它。这个时候如果加载出现了错误,JVM不会立即报错,而是在程序首次主动使用它的时候才报告错误,如果这个类一直没有被主动使用,则一直不会报错。

2. 类的加载过程:

首先,我们都知道类的加载是由类加载器完成的,JVM中加载器分为三类:Bootstrap、Extension、App,其中BootStap类加载器是基于JVM的也就是说不同类型的JVM不一样,可以理解成native;Extension和Application类加载器继承自ClassLoader类是Java代码编写的。每类ClassLoader负责加载不同位置的class

  • Bootstrap ClassLoader(爷爷):jre\lib\rt.jar
    我们知道Java JDK包基本都在rt.jar中,我们可以看到常用的java.lang,java.util都在里面:


    rt.jar
  • Extension ClassLoader(父亲): jre\lib\ext\*.jar
  • App ClassLoader(儿子):它是用来加载在我们环境变量下的class文件。
    这里我们介绍了JVM自带的ClassLoader,最后通过ClassLoader源码来看看类加载器之间是如何工作的。
    前面说了Extension和App类加载器都继承自Classloader.java。在ClassLoader的源码注释中,很好的解释了ClassLoader这个类是做什么工作:

A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.Every Class object contains a reference to the ClassLoader that defined it.

译:类加载器是用来加载类文件的一个对象。ClassLoader这个class本身是一个抽象类,ClassLoader可以根据提供的类名定位到对应的类文件,最典型的方式就是将类名转换成文件名。每一个Class对象都可以访问到生成它的类加载器。

这里的Class对象不就是我们说的加载的最终成果么!!!那么ClassLoader如何工作呢:

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

译:类加载器采用委派模型(即:双亲委派模型)来搜寻类或者资源。每一个类加载器实例都有一个与之关联的父亲加载器。当JVM要求子加载器去找某类或者资源时,子加载器会先委派给父亲加载器,直到顶层的根加载器(BootStrap加载器)
接上面的说,所有的加载任务都会先一级级的传到BootStrap,如果它没找到(类加载器会到自己负责的文件目录中寻找)它会告诉子加载器,子加载器才会开始尝试寻找。它的整个流程如下:


双亲委派机制

在ClassLoader.java中有三个重要方法,对于继承它的类加载器需要去实现:

Class findClass(String name)
Class<?> loadClass
final Class<?> defineClass(String name, byte[] b, int off, int len)

loadClass就是双亲委派的实际过程,我们不妨看看源码:

   protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先判断这个name的class是否加载过,如果加载过c!=null,直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                //如果没有加载过这个类,则开始递归调用父加载器
                long t0 = System.nanoTime();
                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
                }
                //此时c==null,表示父加载器也没有找到Class,这时候就自己找啰
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //父加载器没找到Class,只能自己找,调用自己的findClass()
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

可以看到双亲委派的流程是loadClass控制的,真实的去磁盘上找文件是由findClass()方法执行,但是如果看到findClass定义,你可以发现其中没有内容,这就是各个级别的类加载器需要自己复写的方法。我们在findClass中搜索文件你,如果找到文件,则通过defineClass将文件转化成输入流,进而读到内存方法区中,返回一个Class对象。

以上就是JVM对类加载流程的第一步:加载的完整过程及原理,通过类加载器,磁盘上的class文件就存到了内存方法区当中,下一步就是进行连接和初始化啦!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容

  • 1、classLoader 类加载器,将class文件加载到JVM虚拟机内存中,使得程序可以运行。通常情况下,JV...
    helloWorld_1118阅读 2,202评论 0 2
  • 转发:本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 ClassLoader翻译过来就是类加载...
    尼尔君阅读 532评论 0 1
  • 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 ClassLoader翻译过来就是类加载器,普...
    尼尔君阅读 657评论 1 0
  • ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见...
    时待吾阅读 1,064评论 0 1
  • 0、前言 读完本文,你将了解到: 一、为什么说Jabalpur语言是跨平台的 二、Java虚拟机启动、加载类过程分...
    vivi_wong阅读 1,218评论 0 10