Java知识之JVM
近几年 Java 后端面试中,对 JVM 的考察越来越严格,这也给还没工作经验的应届生带来很大的难度。本期我们就给 JVM 知识部分画一画重点,给大家讲讲如何应付面试官对 JVM 的考察。
救急准备
下面这些问题都是面试互联网大厂后端岗位时的常问问题,我希望你都能熟练的答出来呀!暂时没记牢也不用太担心,我建议你在投简历和约面时,把最想去的公司的面试稍微往后推一推,先面几家自己拿不到 offer 也不会难过的公司。上面这些八股文就是通过我 平时+面试 记熟的,去年 7 月 初第一次我面一家中小厂,这些都磕磕绊绊的答不出来,但是到后期 8 月底 9 月初完全能轻松应对各大厂面试官了。
另外,我通过打星与加粗的方式对下面面试题的重要性进行评级!难度是针对互联网大厂的。
- ⭐ :面试中不常问到,如果面试官问到尽量能答出来,答不出来也没关系。
- ⭐⭐ :面试中不常问到,但是如果面试官问到的话,答不出来对你的印象会减分。
- ⭐⭐⭐:面试中会问到,答不出来面试有点悬。面试官会惊讶为什么你这也不会。
- ⭐⭐⭐⭐:面试高频考点。
- ⭐⭐⭐⭐⭐:面试超高频考点。四星考点和五星考点是参加十场面试,至少能有五场面试问到这些的。大家在准备面试过程中尽量把这些知识点的回答条理梳理清楚,面试官一问就开背。
JVM 基础常见面试题汇总 :
运行时数据区中包含哪些区域?哪些线程共享?哪些线程独享?【⭐⭐⭐⭐⭐】
JDK1.8之前
JDK1.8及之后
线程私有的:
1.程序计数器
2.虚拟机栈
3.本地方法栈
线程共享的:
1.堆
2.方法区
3.直接内存 (非运行时数据区的一部分)说一下方法区和永久代的关系。【⭐⭐⭐】
方法区也被称为永久代。方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。讲一下 Java 创建一个对象的过程。【⭐⭐⭐⭐】
Step1:类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
Step2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
Step4:设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 对象的访问定位的两种方式(句柄和直接指针两种方式)。【⭐⭐⭐⭐⭐】
建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有① 使用句柄和② 直接指针两种:
句柄: 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。你了解分代理论吗?讲一下 Minor GC、还有 Full GC。【⭐⭐⭐⭐⭐】
分代收集
目前的垃圾回收器大多都遵循“分代收集”的理论进行设计,收集器将java堆划分出不同的区域,然后将回收对象依据年龄(存活对象经历的垃圾回收次数)分配到不同的区域之间存储。
目前虚拟机里一般将堆分为新生代和老年代。新生代在每次垃圾回收时都会有大量对象死亡,而存活的对象会逐步晋升到老年代。
分代的好处是垃圾回收器可以每次只回收堆内部分区域。同时针对不同区域对象的特点(垃圾对象数量)使用不同的垃圾回收算法进行有效回收。
根据每次回收的区域不同将回收分为:Minor GC,Major GC,Mixed GC,Full gc.
Minor GC (minor 英[ˈmaɪnə(r)]) 也称为 Young GC.指只对新生代进行垃圾回收。
Major GC (major 英[ˈmeɪdʒə(r)]) 也称为Old GC.指只对老年代进行垃圾回收。
Mixed GC 混合收集,指对整个新生代和部分老年代进行收集。目前只有G1收集器有这种操作。
Full GC 整堆收集,指对整个新生代和老年代以及方法区进行收集。
当进行了分代操作后,在垃圾回收时会存在一个跨代引用的问题。例如新生代引用了老年代,或老年代引用了新生代,在单独收集某一代时会出现不得不扫描另一代的问题。所以如果只进行部分区域垃圾回收时,都会存在跨代引用问题。
当收集新生代时,我们可能要对老年代全盘扫描的问题。由于这种跨代引用的对象理论上是非常少的,所以在新生代上建立了一个全新的数据结构:Remembered Set.这个结构把老年代分成若干小块。记录老年代的哪一块内存存在引用新生代的跨代引用(在对象增加引用关系时维护)。在新生代垃圾回收时,在包含跨代引用的老年代对象会被加入到GC Roots扫描。这样在垃圾回收时比全盘扫描老年代性能上要好一些。
另:
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:
新生代内存(Young Generation)
老生代(Old Generation)
永久代(Permanent Generation)
1.8之后:
JDK 8 版本之后 永久代PermGen 已被 Metaspace(元空间) 取代,元空间使用的是直接内存。
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。Java 用什么方法确定哪些对象该被清理? 讲一下可达性分析算法的流程。【⭐⭐⭐⭐】
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
1.引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
2.可达性分析算法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
下图中的 Object 6 ~ Object 10 之间虽有引用关系,但它们到 GC Roots 不可达,因此为需要被回收的对象。
哪些对象可以作为 GC Roots 呢?
虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈(Native 方法)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
所有被同步锁持有的对象
对象可以被回收,就代表一定会被回收吗?
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。JDK 中有几种引用类型?分别的特点是什么?【⭐⭐】
JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
1.强引用(StrongReference)
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2.软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4.虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。如何回收方法区?【⭐⭐⭐】
方法区的垃圾回收,即使永生代的垃圾收集:主要回收两部分内容,废弃的常量和无用类、
回收废弃的常量与Java堆中的对象非常类似,以常量池中字面量的回收为例:假如一个字符串”abc”已经进入了常量池中,但是当前系统没有任何一个String对象的是叫做abc的,换句话说就是没有任何string对象引用常量池中abc常量,也没有任何其他地方引用这个字面量,如果这时发生内存回收,这个abc常量就会被系统清理出常量池。
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :
1.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
2.加载该类的 ClassLoader 已经被回收。
3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。标记清楚、标记复制、标记整理分别是怎样清理垃圾的?各有什么优缺点?【⭐⭐⭐⭐⭐】
上述为垃圾回收算法。
1.标记-清除算法
该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
效率问题和空间问题(标记清除后会产生大量不连续的碎片)
2.标记-复制算法
为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
3.标记-整理算法
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
4.分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。JVM 中的安全点和安全区各代表什么?写屏障你了解吗?【⭐⭐⭐⭐】
直接PASS。并发标记要解决什么问题?并发标记带来了什么问题?如何解决并发扫描时对象消失问题?【⭐⭐⭐⭐】
相关阅读:面试官:你说你熟悉 jvm?那你讲一下并发的可达性分析 。对于 JVM 的垃圾收集器你有什么了解的?【⭐⭐⭐⭐】
(有时候面试官会问出这种十分开放性的问题,你需要脑子里过一下你对这个大问题下的哪些知识熟悉哪些不熟悉,不熟悉的点一下就过,熟悉的展开讲。在准备校招时,我的一个是阿里 P7 的学姐,给我做过一次模拟面试,问出这个问题时让我有点懵,那么多东西我不知道从哪开始回答呀,就答得很凌乱。模拟面试完我问她这种问题应该从哪开始回答? 她说她因为不知道我的掌握情况,所以就先问一个大问题,根据我的回答再追问,以后遇到这种问题主要从自己熟悉得方面切入就可以了。后来的面试还真遇到过好几次这种情况,我就答,垃圾收集器的种类有以下几种 Serial,ParNew…现在用的多的还是 CMS 和 G1,CMS 的垃圾收集流程是 xxx,G1 的垃圾收集流程是 xxx,他们特点是…就这样把话题引到 CMS 和 G1 了,只 CMS 和 G1 这部分和面试官讨论十几分钟完全没问题。)
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。
1.Serial 收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
2.ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
3.Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)
4.Serial Old 收集器
Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
5.Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
6.CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
7.G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。新生代垃圾收集器有哪些?老年代垃圾收集器有哪些?哪些是单线程垃圾收集器,哪些是多线程垃圾收集器?各有什么特点?各基于哪一种垃圾收集算法?【⭐⭐⭐⭐】
直接PASS。讲一下 CMS 垃圾收集器的四个步骤。CMS 有什么缺点?【⭐⭐⭐⭐】
CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
对 CPU 资源敏感;
无法处理浮动垃圾;
它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。G1 垃圾收集器的步骤。有什么缺点?【⭐⭐⭐⭐】
G1 收集器的运作大致分为以下几个步骤:
初始标记
并发标记
最终标记
筛选回收讲一下内存分配策略?【⭐⭐⭐⭐】
三大原则+担保机制#
JVM分配内存机制有三大原则和担保机制
具体如下所示:
优先分配到eden区
大对象,直接进入到老年代
长期存活的对象分配到老年代
空间分配担保虚拟机基础故障处理工具有哪些?【⭐⭐⭐】
jps (JVM Process Status): 类似 UNIX 的 ps 命令。用于查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;
jstat(JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;
jinfo (Configuration Info for Java) : Configuration Info for Java,显示虚拟机配置信息;
jmap (Memory Map for Java) : 生成堆转储快照;
jhat (JVM Heap Dump Browser) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;
jstack (Stack Trace for Java) : 生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。什么是字节码?类文件结构的组成了解吗?【⭐⭐⭐⭐】
JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。
类文件结构的组成:直接PASS。类的生命周期?类加载的过程了解么?加载这一步主要做了什么事情?初始化阶段中哪几种情况必须对类初始化?【⭐⭐⭐⭐⭐】
一个类的完整生命周期如下:
Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
其中:
类加载过程的第一步,主要完成下面 3 件事情:
1.通过全类名获取定义此类的二进制字节流
2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口讲一下双亲委派模型。【⭐⭐⭐⭐⭐】
每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。
其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。
系统学习
如果想要看书学习 JVM 和准备 JVM 面试的话,我建推荐两本书:一本是准备后端面试时人手一本的《深入理解 Java 虚拟机》,另一本是《实战 Java 虚拟机》。
《深入理解 Java 虚拟机》 这本书的推荐理由不用多说,面试官的 JVM 知识也是从这本书上学的。推荐实战 Java 虚拟机这本书的理由呢,是因为深入理解 Java 虚拟机这本书初学者看起来有点困难。如果你一点 JVM 虚拟机基础都没有,那么我建议你先快速过一遍实战 Java 虚拟机,然后再去啃深入理解 Java 虚拟机这本书。我的理念一直是先易后难,先做到上手再做到深挖。深入理解 Java 虚拟机这本书很厚,但是通过我的划重点,能把书变薄。大家先把我划重点的部分学会,就完全够应付面试了,剩下的部分有时间再看就好了。
这里我们以 《深入理解 Java 虚拟机》 这本书为例来介绍如何准备 JVM 面试八股文。
第 2 章 Java 内存区域与内存溢出异常
第 2 章属于第二部分的一个总概括,也是 JVM 八股文面试中的一大考点。首先,运行时数据区
是面试官特别爱问的一个问题。下面这张图大家一定要记牢。
面试官在考察 JVM 部分时,起始问题一般就是运行时数据区中包含哪些区域?哪些区域时线程共享?哪些区域线程隔离?然后以此再问你各个部分的细节。这几个区域中,堆和方法区又是考察的重点。另外虚拟机在 Java 堆中 对象的创建
、内存布局
、对象访问
定位这几个过程也是面试八股文的一大考点。如果有时间的话把 2.4 节 OutOfMemoryError 异常的实例了解一下也是挺好的。
第 3 章 垃圾收集器与内存分配策略
垃圾收集器是面试中重点的重点。首先 引用计数法
和 可达性分析算法
必须充分理解,然后强引用
、软引用
、 弱引用
、虚引用
四种类型必须理解并且记牢,面试官特别爱问四种引用的区别。垃圾收集算法中的分代收集理论要充分理解并且记熟,标记-清楚算法
、标记复制算法
、标记整理算法
要充分理解并且记熟,并比较各自的优缺点。根节点枚举
、安全点
、安全区域
、记忆集与卡表
、写屏障
、并发的可达性分析
要充分理解并能大致复述出来。 经典垃圾收集器 CMS
和 G1
相关知识点要充分理解并且记熟(这是超高频考点),Serial
、ParNew
等了解就好,新生代的垃圾收集器暂时就别看了,你不说面试官不会问的。3.8 节的内存分配与回收策略的实战要好好看下,对象优先在 Eden
分配、大对象直接进入老年代
、长期存活的对象进入老年代
、动态对象年龄判定
、空间分配担保
这些技术做了什么要记清楚。这些技术使用的常见参数有哪些?
第 4 章 虚拟机性能监控、故障处理工具
这一章有时间看下,把几个常用的工具记一下,没时间就别看了。当时有个面试官问我虚拟机怎么做性能监控,我说我知道有工具可以监控虚拟机性能,但名字我记不清了,面试官说你知道有工具就行。
第 5 章 调优案例分析与实战
在这一章可以学一两个实例套到自己项目中给面试官讲,但是如果你感觉自己给面试官讲不清楚,那么千万别勉强。你别说你有 JVM 调优经验,面试官一般也不会问你。
第 6 章 类文件结构
大致扫读一遍就可以了,了解就行,面试官基本不问。
第七章 虚拟机类加载机制
类的生命周期
一定要记牢,就是下面这张图。
上述环节的顺序要记清楚,每个环节做了什么也要记清楚。比如 加载
阶段做了三件事:
(1)通过类的全限定名获取定义此类的二进制字节流。
(2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构。
(3)在内存中生成一个代表这个类的 Class 对象作为方法区这个类的各种数据访问入口。其它几个环节同样,不需要记清楚细节,但是做了什么要记清楚。
类加载器中的 双亲委派模型
是八股文考试中重点的重点,被问到的频率特别高,一定要充分理解,并且记熟。
说到类加载,Java创建对象
的过程也一定要记熟呀,类加载检查 -> 分配内存 -> 初始化零值 -> 设置对象头 ->执行 init 方法,这个顺序一定要记牢,细节也要记清。
第八章 虚拟机字节码执行引擎
这一章把 栈帧的结构
弄清楚就差不多了,其它的就扫一眼就好了。
第九章 类加载及执行子系统的案例与实战
了解一下就可以了,不了解也没事。
第十二章 Java 内存模型与线程
这一章也是八股文面试考察中的重点、Java内存模型的定义
、内存间交互操作
要记清楚。volatile
这个关键字是面试考察的重点,他的作用要彻彻底底的掌握,面试中经常问。Java 内存模型的三大特征 原子性、可见性和有序性
也一定要理解。Java 线程的状态转换
也是一个常考问题。协程了解一下就好了。
第十三章 线程安全与锁优化
这部分绝对是面试中考察的重点中的重点。首先你需要理解并记牢线程安全的几种实现方法,比如互斥同步
、非阻塞同步
等。互斥同步中有两个重要的同步手段一定要重视,一个是 synchronized
,另一个是 ReentrantLock
。非阻塞同步中一个重要手段 CAS
一定要充分理解。
锁优化也是考察的重点,适应性自旋
、锁消除
、锁碰撞
、轻量级锁
、偏向锁
这些技术一定要充分理解和记熟。
好了,如果只是为了通过 JVM 的八股文面试的话,把上面我划重点的内容掌握应付校招足够了。这样看,深入理解 Java 虚拟机这本书是不是变薄了很多了? 但是如果有时间的话,我还是建议你能够把整本书都好好看下呀。嗯…我知道你们应该学累了。
下面我出一些问题,把我上面说的学完以后,你可以用来自测一下你 JVM 准备的怎么样了,可以收藏了等面试前再过一遍。答案都在深入理解 Java 虚拟机这本书中。
如果你比较喜欢看视频学习的话,推荐你看一下尚硅谷的尚硅谷的宋红康老师的《JVM 全套教程》。这个课程的内容非常硬,一共有接近 400 小节。
这门课程主要讲的是 JVM 理论相关的内容,不过也会结合部分实践来加深理解。
讲真,宋红康老师讲解的非常通俗易懂,配合大量的图解非常容易让人理解。宋老师 YYDS!
课程的内容分为 3 部分,基本把 JVM 中重要的知识点都涵盖到了!
- 《内存与垃圾回收篇》
- 《字节码与类的加载篇》
- 《性能监控与调优篇》
我知道有很多小伙伴学习 JVM 主要是为了应付面试,毕竟很多大厂在招聘 Java 开发的时候,JVM 基本是必问。
为了节省本就宝贵的面试复习时间,对于找工作面试的同学,看 p01-p203 ,p266-p301 就可以了。
尚硅谷算得上是比价良心的培训机构了,免费开源了很多免费且高质量的教学视频,帮助了很多小伙伴学习编程。