类加载器工作原理

1.首先咱们来聊一聊什么叫类加载器

顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中,具体来说是加载.class文件到jvm内存。java源代码(.java文件)在经过java编译器编译(javac 指令)之后会生成一个或多个的.class文件,当需要生成该对象的实例时,虚拟机会去常量池查找该类是否被加载,如果没有被加载,就会调用类加载器来将.class文件中的二进制流加载到内存中。而加载二进制流的工具 (实现这一动作的代码块)就是我们所说的类加载器。

2.类加载器的作用:

从上面可以知道,类加载器干的活就是将对应类的.class文件中的二进制流加载到内存空间。

注意:这里所说的加载到内存空间只是将二进制流写入内存还并没有将二进制流的存储结构解析并写入方法区,这一步操作是在  【类加载】   【验证阶段】 【文件格式验证阶段】才完成

3.类与类加载器之间的关系

对于任意的一个类,都需要由加载它的类加载器这个类本身一同确立其在java虚拟机中唯一性

每一个类加载器,都拥有一个独立的类名称空间 (这也是为什么每个类的初始化只会执行一次的原因)

通俗的来讲:比较两个类是否“相等”只有这两个类由同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于同一个.class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那么这两个类就必然不相等。


那么问题来了:java如何判断两个对象是否"相等"?

分析这个问题前先来复习两个问题,对象的比较的  ==  与  equals

大家都知道用 ==来比较是直接判断当前比较的两个对象是不是同一个对象,也就是当前两个对象的指针指向的是不是同一片内存区域,简单来说:== 的比较方式判断的是 内存地址 也就是  是不是同一个对象。因为虚拟机中在同一时刻一个内存块只能存放一个对象,也就是说内存地址是唯一的。

equals的比较方法在继承自Object的子类没有重写equals方法之前调用的是父类也就是Object的equals方法。而Object的equals方法实现使用的就是 == 判断两个对象是不是同一个对象。

源码:

public boolean equals(Object obj) {    // Object的equals方法实现

                 return ( this  ==  obj);

}

如果子类重写了equals方法,则调用的是子类自己的实现逻辑,我们一般把equals比较的结果当成是对象的值的比较,也就是 equals比较的是对象的  值

hashcode与equals的关系

Object中有个 hashcode方法,这个方法默认返回的是内存地址生成的一个 int 值(虚拟机的实现不同,可能有出入),也就是说默认的hashcode是唯一的(因为内存地址唯一)

但是如果重写了Object的hashcode方法,那么hashcode的生成规则是由子类自己决定,与内存地址就无关了(只是与当前对象的内存地址无关了,其实质还是通过对象的某些字段的内存地址生成的int值)。

所以对象是否相等这里要分成三种情况来讨论

1.没有重写 对象的equals 与 hashcode 方法

如果对象没有重写equals方法,那么比较的是两个对象的 hashcode(内存地址根据一定的规则生成),实质上也就是对象的内存地址,更通俗一点就是两个  是不是同一个对象

此处 hashcode 不相等,像个对象必然不相等 ,反之亦然

2.重写了 equals方法,但是没有重写hashcode方法

如果重写了equals方法,那么我们就是根据 自己定义的equals方法 比较对象的值。由于没有重写hashcode方法,那么hashcode的生成规则依旧是根据  内存按照一定规则生成

此处分为两种情况:

1.如果两个对象的equals 方法为真,但他们的 hashcode不一定相同.

2.如果两个对象的hashcode相同,那么他们是同一个对象,当然equals会为真

3.重写了equals 与 hashcode 方法

重写了hashcode方法,那么hashcode就是根据自己定义的规则生成,与内存地址无关了

如果重写了equals与hashcode方法那么比较的规则就是与Object的基本一致,唯一不同的是,调用的是子类中的equals与hashcode方法

此处如果hashcode相同,那么两个对象比较的euqls方法为真,反之亦然

所以在Object的equals方法的注释里有这么一段话:

* Note that it is generally necessary to override the {@code hashCode}

* method whenever this method is overridden, so as to maintain the

* general contract for the {@code hashCode} method, which states

* that equal objects must have equal hash codes.

大概意思也就是:为了维护 hashcode 方法的一般合约,即:相同的对象必须要具备相同的哈希值在重写了equals 方法后   有必要   重写 hashcode方法注意这里是有必要,没有一定 强制重写。

重写了equals却没有重写hashcode,一般不会有什么问题,但要是要用到hashcode作为比较的条件的时候才会有问题,比方说对象作为map的Key等。

hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。


扯远了,言归正传。上面讲到了类加载器的原理与作用,下面来分析一下类加载器的类型。

4.类加载器的分类

从java虚拟机的角度来讲,只分为两种类加载器,一种叫做启动类加载器,这个加载器有C++实现,是虚拟机的一部分。另外一种就是其他的类加载器,这些类都是有java语言实现,独立于虚拟机之外,并且都有一个共同点:继承自抽象类java.lang.ClassLoader

从java开发人员的角度来看,分的更加细致,绝大部分的java程序都会提供以下三种系统类加载器。

1)启动类加载器(Bootstrap ClassLoader 引导类加载器),这个类负责将放在<JAVA_HOME>\lib目录下,并且是虚拟机识别的(仅仅按照文件名识别,名字不符合的类库即使在lib目录下也不会被夹在)类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用,如果要把加载请求委派给启动类加载器,那就直接用null代替即可。

2)扩展类加载器(Extension ClassLoader)负责加载<JAVA_HOME>\lib\ext目录下的类库

3)应用程序类加载器(Application ClassLoader)也称为系统类加载器,负责加载用户类路径上所指定的类库,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。通俗的来讲:一般情况下,我们自己写的类是由这个加载器加载。

双亲委派模型  如下图所示:

双亲委派模型

这个模型执行的总体意思也就是:如果一个类收到了类加载请求,它首先自己不会去加载这个类,而是将这个请求委派给父类的加载器去完成,父类的加载器也不会首先去加载,他会将这个请求委派给自己的父类去完成,如此往复直到顶层的 启动类加载器。只有当父类加载器反馈说自己无法完成这个加载请求时(在它的搜索范围类没有找到所需的类),父类会一层一层往下通知,子类加载器才会自己去加载这个类。

通俗来讲就是这样:

儿子需要一个玩具月亮,就跟老爹讲,老爹懒得理他,就跟自己的老爹说,你孙子要个月亮,爷爷一听孙子要个月亮,就在自己力所能及的范围内搜索,结果发现自己找不到,然后爷爷就对爸爸说,儿子,我找不到孙子要的月亮,还是你自己来找吧,于是爸爸也在自己的搜索范围类搜索,发现自己也找不到这个月亮,于是也对儿子说,儿子,你老子找不到这个月亮,这个事儿呀还是得你自己来,儿子一听你们都找不到那还是我自己来吧,自己找找找呀找到了,拿着月亮玩去了,要是找遍了没找到,儿子就躲一边哭(报找不到类的异常)

这个称之为双亲委派模型,这个模型的作用是java类随着他得类加载器一起具备了一种带有优先层次的关系。例如类java.lang.Object,它存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给顶端的启动类加载器去加载,因此Object类在程序的各种类加载环境中都是同一个类。相反,如果不是用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个java.lang.Object的类,并放在classPath中,那么系统中将出现多个不同的Object类,java体系中最基础的行为也无法保证。

双亲委派模型对于保证java程序的稳定运行很重要,但实现对相当简单逻辑很清楚:先检查类是否被加载过,若没有就调用父加载器的loadClass方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出异常,再调用自己的findClass方法进行加载。

双亲委派模型中的父子关系不是由继承关系实现的,而是以组合关系来复用父加载器的代码

双亲委派模型被广泛应用于之后几乎所有的java程序,但却并不是一个强制的模型,而是设计者推荐给开发者的一种类加载实现方式

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