不止面试—jvm类加载面试题详解

面试题

带着问题学习是最高效的,本次我们将尝试回答以下问题:

  1. 什么是类的加载?
  2. 哪些情况会触发类的加载?
  3. 讲一下JVM加载一个类的过程
  4. 什么时候会为变量分配内存?
  5. JVM的类加载机制是什么?
  6. 双亲委派机制可以打破吗?为什么

答案放在文章的最后,来不及看原理也可以直接跳到最后直接看答案。

深入原理

类的生命周期

类的生命周期相信大家已经耳熟能详,就像下面这样:

file

不过这东西总是背了就忘,忘了又背,就像马什么梅一样,对吧?

其实理解之后,基本上就不会再忘了。

加载

加载主要做三件事:

  1. 找到类文件(通过类的全限定名来获取定义此类的二进制字节流)
  2. 放入方法区(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)
  3. 开个入口(生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口)

总的来讲,这一步就是通过类加载器把类读入内存。需要注意的是,第三步虽然生成了对象,但并不在堆里,而是在方法区里。

连接

连接分为三步,一般面试都比较喜欢问准备这一步。

校验

顾名思义,检查Class文件的字节流中包含的信息是否符合当前虚拟机的要求。

准备

这一步中将为静态变量和静态常量分配内存,并赋值。

需要注意的是,静态变量只会给默认值。比如下面这个:

public static int value = 123;

此时赋给value的值是0,不是123。

静态常量(static final修饰的)则会直接赋值。比如下面这个:

public static final int value = 123;

此时赋给value的值是123。

解析

解析阶段就是jvm将常量池的符号引用替换为直接引用。

恩......啥是常量池?啥是符号引用?啥是直接引用?

常量池我们放在jvm内存结构里说。先来说下什么是符号引用和直接引用。

符号引用和直接引用

假设有一个Worker类,包含了一个Car类的run()方法,像下面这样:

class Worker{
    ......
    public void gotoWork(){
        car.run(); //这段代码在Worker类中的二进制表示为符号引用        
    }
    ......
}

在解析阶段之前,Worker类并不知道car.run()这个方法内存的什么地方,于是只能用一个字符串来表示这个方法。该字符串包含了足够的信息,比如类的信息,方法名,方法参数等,以供实际使用时可以找到相应的位置。

这个字符串就被称为符号引用

在解析阶段,jvm根据字符串的内容找到内存区域中相应的地址,然后把符号引用替换成直接指向目标的指针、句柄、偏移量等,这之后就可以直接使用了。

这些直接指向目标的指针、句柄、偏移量就被成为直接引用

初始化

类的初始化的主要工作是为静态变量赋程序设定的初值。

还记得上面的静态变量吗:

public static int value = 123;

经过这一步,value的值终于是123了。

总结如下图:

file

类初始化的条件

Java虚拟机规范中严格规定了有且只有五种情况必须对类进行初始化:

  1. 使用new字节码指令创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。
  2. 通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。
  3. 当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化。
  4. 当虚拟机启动时,用户需要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类。
  5. 使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。

除了以上这五种情况,其他任何情况都不会触发类的初始化。

比如下面这几种情况就不会触发类初始化:

  1. 通过子类调用父类的静态字段。此时父类符合情况一,而子类不符合任何情况。所以只有父类被初始化。
  2. 通过数组来引用类,不会触发类的初始化。因为new的是数组,而不是类。
  3. 调用类的静态常量不会触发类的初始化,因为静态常量在编译阶段就会被存入调用类的常量池中,不会引用到定义常量的类。

类加载机制

类加载器

在上面咱们曾经说到,加载阶段需要“通过一个类的全限定名来获取描述此类的二进制字节流”。这件事情就是类加载器在做。

jvm自带三种类加载器,分别是:

  1. 启动类加载器。
  2. 扩展类加载器。
  3. 应用程序类加载器

他们的继承关系如下图:

file

双亲委派

双亲委派机制工作过程如下:

  1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

  2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.

  3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。

为啥要搞这么复杂?自己处理不好吗?

双亲委派的优点如下:

  1. 避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 为了安全。避免核心类,比如String被替换。

打破双亲委派

“双亲委派”机制只是Java推荐的机制,并不是强制的机制。

比如JDBC就打破了双亲委派机制。它通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类,从而打破了双亲委派机制。

至于为什么,以后再说吧。

答案

现在,我们可以回答文章开头提出的问题了。尽量在理解的基础上回答,不需要死记硬背。

  1. 什么是类的加载?

    JVM把通过类名获得类的二进制流之后,把类放入方法区,并创建入口对象的过程被称为类的加载。经过加载,类就被放到内存里了。

  2. 哪些情况会触发类的初始化?

    类在5种情况下会被初始化:

    第一,假如这个类是入口类,他会被初始化。

    第二,使用new创建对象,或者调用类的静态变量,类会被初始化。不过静态常量不算。

    第三,通过反射获取类,类会被初始化

    第四,如果子类被初始化,他的父类也会被初始化。

    第五,使用jdk1.7的动态语言支持时,调用到静态句柄,也会被初始化。

  3. 讲一下JVM加载一个类的过程

    同问题1。不过这里也可以问下面试官是不是想问类的生命周期。如果是问类的生命周期,可以回答有”加载、连接、初始化、使用、卸载“五个阶段,连接又可以分为”校验、准备、解析“三个阶段。

  4. 什么时候会为变量分配内存?

    在准备阶段为静态变量分配内存。

  5. JVM的类加载机制是什么?

    双亲委派机制,类加载器会先让自己的父类来加载,父类无法加载的话,才会自己来加载。

  6. 双亲委派机制可以打破吗?为什么

    可以打破,比如JDBC使用线程上下文加载器打破了双亲委派机制。原因是JDBC只提供了接口,并没有提供实现。这个问题可以再看下引用文献的内容。

引用文献

Java什么时候会触发类初始化及原理

Java 符号引用 与 直接引用

符号引用和直接引用

JVM中的直接引用和符号引用

面试题:JVM类加载机制详解(一)JVM类加载过程

深入理解双亲委托机制

双亲委派模式破坏-JDBC

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

推荐阅读更多精彩内容

  • 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,确实编译语言发展的一大步。 虚拟机把描述类的数据从...
    胡二囧阅读 954评论 0 0
  • JVM类加载机制 概述 类加载过程 加载 通过类的全限定名获取类的二进制流 将静态存储结构转化为方法区的运行时数据...
    东溪95阅读 3,033评论 0 15
  • JVM类结构类加载类执行 类加载的五个过程:加载、验证、准备、解析、初始化。 加载: 根据全限定名来获取定义类的二...
    Fitz_Lee阅读 235评论 0 1
  • 在Class文件中描述了各种信息,最终都需要加载到虚拟机中才能运行和使用,而虚拟机是如何加载这些Class文件呢?...
    小胖学编程阅读 551评论 0 3
  • 217. Contains Duplicate https://leetcode.com/problems/con...
    Double_E阅读 535评论 0 0