不止面试—jvm类加载面试题详解
面试题
带着问题学习是最高效的,本次我们将尝试回答以下问题:
- 什么是类的加载?
- 哪些情况会触发类的加载?
- 讲一下JVM加载一个类的过程
- 什么时候会为变量分配内存?
- JVM的类加载机制是什么?
- 双亲委派机制可以打破吗?为什么
答案放在文章的最后,来不及看原理也可以直接跳到最后直接看答案。
深入原理
类的生命周期
类的生命周期相信大家已经耳熟能详,就像下面这样:
不过这东西总是背了就忘,忘了又背,就像马什么梅一样,对吧?
其实理解之后,基本上就不会再忘了。
加载
加载主要做三件事:
- 找到类文件(通过类的全限定名来获取定义此类的二进制字节流)
- 放入方法区(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)
- 开个入口(生成一个代表此类的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了。
总结如下图:
类初始化的条件
Java虚拟机规范中严格规定了有且只有五种情况必须对类进行初始化:
- 使用new字节码指令创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。
- 通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。
- 当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化。
- 当虚拟机启动时,用户需要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类。
- 使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。
除了以上这五种情况,其他任何情况都不会触发类的初始化。
比如下面这几种情况就不会触发类初始化:
- 通过子类调用父类的静态字段。此时父类符合情况一,而子类不符合任何情况。所以只有父类被初始化。
- 通过数组来引用类,不会触发类的初始化。因为new的是数组,而不是类。
- 调用类的静态常量不会触发类的初始化,因为静态常量在编译阶段就会被存入调用类的常量池中,不会引用到定义常量的类。
类加载机制
类加载器
在上面咱们曾经说到,加载阶段需要“通过一个类的全限定名来获取描述此类的二进制字节流”。这件事情就是类加载器在做。
jvm自带三种类加载器,分别是:
- 启动类加载器。
- 扩展类加载器。
- 应用程序类加载器
他们的继承关系如下图:
双亲委派
双亲委派机制工作过程如下:
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
为啥要搞这么复杂?自己处理不好吗?
双亲委派的优点如下:
- 避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 为了安全。避免核心类,比如String被替换。
打破双亲委派
“双亲委派”机制只是Java推荐的机制,并不是强制的机制。
比如JDBC就打破了双亲委派机制。它通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类,从而打破了双亲委派机制。
至于为什么,以后再说吧。
答案
现在,我们可以回答文章开头提出的问题了。尽量在理解的基础上回答,不需要死记硬背。
什么是类的加载?
JVM把通过类名获得类的二进制流之后,把类放入方法区,并创建入口对象的过程被称为类的加载。经过加载,类就被放到内存里了。
哪些情况会触发类的初始化?
类在5种情况下会被初始化:
第一,假如这个类是入口类,他会被初始化。
第二,使用new创建对象,或者调用类的静态变量,类会被初始化。不过静态常量不算。
第三,通过反射获取类,类会被初始化
第四,如果子类被初始化,他的父类也会被初始化。
第五,使用jdk1.7的动态语言支持时,调用到静态句柄,也会被初始化。
讲一下JVM加载一个类的过程
同问题1。不过这里也可以问下面试官是不是想问类的生命周期。如果是问类的生命周期,可以回答有”加载、连接、初始化、使用、卸载“五个阶段,连接又可以分为”校验、准备、解析“三个阶段。
什么时候会为变量分配内存?
在准备阶段为静态变量分配内存。
JVM的类加载机制是什么?
双亲委派机制,类加载器会先让自己的父类来加载,父类无法加载的话,才会自己来加载。
双亲委派机制可以打破吗?为什么
可以打破,比如JDBC使用线程上下文加载器打破了双亲委派机制。原因是JDBC只提供了接口,并没有提供实现。这个问题可以再看下引用文献的内容。
引用文献
不止面试—jvm类加载面试题详解的更多相关文章
- 不止面试02-JVM内存模型面试题详解
第一部分:面试题 本篇文章我们将尝试回答以下问题: 描述一下jvm的内存结构 描述一下jvm的内存模型 谈一下你对常量池的理解 什么情况下会发生栈内存溢出?和内存溢出有什么不同? String str ...
- 《Java面试全解析》505道面试题详解
<Java面试全解析>是我在 GitChat 发布的一门电子书,全书总共有 15 万字和 505 道 Java 面试题解析,目前来说应该是最实用和最全的 Java 面试题解析了. 我本人是 ...
- JVM之内存结构详解
对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug.同时,JVM也是面试环节的中重灾区.今天开始,<JVM详解>系列开启,带大家深入了解JVM相 ...
- JVM性能调优详解
前面我们学习了整个JVM系列,最终目标的不仅仅是了解JVM的基础知识,也是为了进行JVM性能调优做准备.这篇文章带领大家学习JVM性能调优的知识. 性能调优 性能调优包含多个层次,比如:架构调优.代码 ...
- [转帖]JVM性能调优详解
JVM性能调优详解 https://www.cnblogs.com/secbro/p/11833651.html 应该是 jdk8 以前的方法 貌似permsize 已经放弃这一块了. 前面我们学习了 ...
- 不止面试-JVM垃圾回收面试题详解
第一部分:面试题 本次分享我们将尝试回答以下问题: GC 是什么? 为什么要有 GC? 简单说一下java的垃圾回收机制. JVM的常见垃圾回收算法有哪些? 为什么要使用分代回收机制? 如何判断一个对 ...
- JVM——三个ClassLoader详解
类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的Java执行时系统组件,它负责在运行时查找和装入Class字节码文件.JVM在运行时会产生三个ClassLoader: ...
- JVM的GC理论详解
GC的概念 GC:Garbage Collection 垃圾收集.这里所谓的垃圾指的是在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM(堆溢出) ...
- JVM性能分析工具详解--MAT等
获得堆转储文件 巧妇难为无米之炊,我们首先需要获得一个堆转储文件.为了方便,本文采用的是 Sun JDK 6.通常来说,只要你设置了如下所示的 JVM 参数: -XX:+HeapDumpOnOutOf ...
随机推荐
- CSS中颜色表示方法及颜色表
一:直接用颜色的英文名表示 二:RGB(x,y,z)函数表示,x.y.z.分别是红色.绿色.蓝色的值,x,y,z∈[0,225],亦可用百分比表示:对比rgba(x,y,z,a)中a是alpha通道设 ...
- 2019年高级Java程序员面试题汇总
目录 JDK Dubbo Zookeeper Strut2 Spring系列 Redis系列 Mysql系列 Java多线程 消息中间件 线程池 事物 JVM 设计模式 其他 程序设计 基础知识 编程 ...
- 利用git工具将自己的代码文件上传到Github
GitHub 是一个面向开源及私有软件项目的托管平台,作为开源代码库以及版本控制系统,Github拥有超过900万开发者用户.随着越来越多的应用程序转移到了云上,Github已经成为了管理软件开发以及 ...
- 微信小程序之页面引用utils中的js文件
/* 只可使用相对路径 */const utils = require('../../../utils/util.js') Page({})
- Linux配置部署_新手向(五)——Docker的安装与使用
前言 最近还是在考虑Linux下net core的部署问题,还是发现了很多麻烦的问题,这里还是继续把需要使用的东西部署介绍下吧. Docker 其实对于Docker我也是一星半点儿,了解的不够深入,大 ...
- Spring框架学习笔记(5)——Spring Boot创建与使用
Spring Boot可以更为方便地搭建一个Web系统,之后服务器上部署也较为方便 创建Spring boot项目 1. 使用IDEA创建项目 2. 修改groupid和artifact 3. 一路n ...
- python wraps的作用
1.__name__用来显示函数的名称,__doc__用来显示文档字符串也就是("""文档字符串""")这里面的内容 2.首先我们来看不加@ ...
- Dockerfile 指令详解
GitHub Page:https://blog.cloudli.top/posts/Dockerfile-指令详解/ FROM FROM 命令指定基础镜像.在构建镜像时,基础镜像必须指定,因此在 D ...
- Google Tensorflow 迁移学习 Inception-v3
附上代码加数据地址 https://github.com/Liuyubao/transfer-learning ,欢迎参考. 一.Inception-V3模型 1.1 详细了解模型可参考以下论文: [ ...
- NodeJs 实现 WebSocket 即时通讯(版本一)
服务端代码 var ws = require("nodejs-websocket"); console.log("开始建立连接...") var game1 = ...