之前看面经分享帖的时候,学到了已经上岸大厂的前辈的做法。在准备暑期实习时,我也效仿着根据以往的真实面经整理八股。从牛客、小破站等各个平台搜集了上千篇真实面经,自己整理得到了面试题,根据题目在面试题中出现的频率以及我自己、交流群、好朋友面试被问到的频率进行了分类整理,得到⭐🌟💡三种级别的。在此,给大家分享一下我自己面试被问到的题目,以及我根据以往面经整理得到的题目。各位uu可在专栏关筑一波:
https://www.nowcoder.com/creation/manager/columnDetail/Mq7Xxv
牛客 Top 博主都订阅了,比如“Java 抽象带篮子”(7000+ 粉丝),在这里感谢篮子哥的支持!
所有内容经过科学分类与巧妙标注,针对性强,让你的学习事半功倍:
- ⭐ 必须掌握(必看):时间紧迫时的救命稻草,优先攻克核心要点。(可参考神哥的高频题,但我整理出来的比神哥还会多一些,另外还包括以下内容)
- 🌟 尽量掌握(有时间就看):适合两周以上备考时间的同学稳步提升,冲击大厂的uu们建议看!
- 💡 了解即可(知识拓展):时间充裕时作为补充,拓宽视野,被问到的概率小,但如果能答出来就是加分项
- 🔥 面试真题:根据真实面经整理出来的面试题,有些可能难度很高,可根据自身水平酌情参考。
按照推荐观看顺序 “🔥⭐> 🔥🌟 > > 🔥💡” 有条不紊地学习,让每一分每一秒都用在刀刃上,自此一路畅行。
全面覆盖面试核心知识
我的面试真题涵盖技术领域的核心考点,从高频热点到冷门难点一网打尽。以下是部分模块概览:
Java基础&集合 :
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=4f5b4cac4b9f4dee8b4b213851c154c5
JVM篇:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=c87d9ad65eb840728ae63774893bccf5
Java并发编程&JUC:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=28c748189f6b471f9f4218791778f41c
MySQL:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=55b03d6d16604319a24395f393d615be
Redis:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=77bd828f85984c22858c3724eef78723
计网:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=65e9951c2e754d7086d26b9b46aa4a1a
后续还将持续更新 操作系统、设计模式、场景题、智力题等丰富内容
独特解析:知其然,更知其所以然
我整理的八股面经绝非简单的问答堆砌。
每一道题目都配有深度剖析的思考过程,在你看题之前,便清晰呈现出题意图,让你迅速抓住题目核心,加深对题目的理解与记忆,做到 “知己知彼,百战不殆”。
Java基础&集合举例
MySQL
Redis
JVM
Java并发(JUC)
计算机网络
助力你举一反三,深度梳理知识点之间的内在逻辑联系,真正实现知识的融会贯通,做到知其然更知其所以然。
后续还会分享如何包装项目、leetcode 刷题模版与刷题技巧、各种学习经验以及真实面经等,从多个角度助力牛u提升技术能力和面试水平。
还是那句话:
1、简历上相关技术点对应的面试题一定要准备,因为写在简历上了,面试官就默认你会,答不上来的话就很减分
2、抓大放小,优先重点高频八股,根据自身情况进行准备。
更新日志
4.7更新:JVM基础&内存区域
4.9更新:类加载相关
4.11更新:垃圾回收(可达性分析、垃圾回收算法、垃圾回收器等)
4.13更新:垃圾回收另外一部分&调优
目前更新完毕,等待后续补充中....
JVM基础&内存区域(主要考察内存区域结构)
🔥⭐Q: 编译执行和解释执行有什么不同?JVM 采用了哪种方式?
思考过程:
这个问题考察对 程序执行方式的理解以及 JVM 的执行模式。需要说明编译执行和解释执行的特点,并解释 JVM 混合使用这两种方式的原因。
- 编译执行: 提前将源代码编译成机器码,运行速度快,但跨平台性差。
- 解释执行: 逐行解释执行源代码,跨平台性好,但运行速度相对较慢。
- JVM 的方式: 结合了解释执行和即时编译 (JIT)。
- 解释执行的优势: 程序启动快,跨平台。
- JIT 的优势: 将热点代码编译成机器码,提高性能。
回答提问:
好的面试官,编译执行是指在程序运行之前,先通过编译器将源代码完整地翻译成机器代码,然后 CPU 直接执行这些机器代码。像 C 和 C++ 就属于编译型语言。它的优点是运行速度快,因为已经提前翻译好了。缺点是跨平台性相对较差,因为需要针对不同的平台进行编译。
解释执行则是指源代码在运行的时候,通过解释器逐行地读取、翻译并执行。像 Python 和 JavaScript 就属于解释型语言。它的优点是跨平台性好,因为只需要在不同的平台上安装相应的解释器即可运行。缺点是运行速度相对较慢,因为每次执行都需要进行动态翻译。
对于 JVM 来说,它结合了编译执行和解释执行这两种方式。通常情况下,JVM 会先对字节码进行解释执行,这样可以使得程序启动更快,并且保证了 跨平台性。但是,JVM 还会引入 即时编译器 (Just-In-Time Compiler, JIT),它会在程序运行过程中,将那些频繁执行的热点代码编译成本地机器码,这样在后续执行这些代码时,就可以直接运行机器码,从而提高程序的性能。所以,JVM 实际上是混合使用解释执行和编译执行的。
🔥⭐Q: 能详细地谈谈 JVM 的内存结构吗?
思考过程:
这个问题考察对 JVM 运行时数据区域的理解。需要区分线程私有和线程共享的内存区域,并说明每个区域的作用和可能发生的异常。
- 线程私有:Java 虚拟机栈: 存储栈帧,用于方法执行。可能抛出 StackOverflowError。本地方法栈: 为 native 方法服务。在 HotSpot 中与 Java 虚拟机栈合二为一。程序计数器: 记录当前执行的字节码指令地址。唯一不会发生 OutOfMemoryError 的区域。
- 线程共享:堆: 存储对象实例和数组。可能抛出 OutOfMemoryError。细分为新生代(Eden、Survivor)和老年代。方法区/元空间: 存储类的结构信息、常量池等。JDK 8 之后是元空间,使用本地内存。可能抛出 OutOfMemoryError。
回答提问:
好的面试官,JVM 对内存的管理是采用分区的模式,主要分为线程私有和线程共享两大区域:
线程私有的内存区域包括:
- Java 虚拟机栈(JVM Stack):每个线程都有自己独立的 JVM 栈,它是一个后进先出的栈结构。每个方法在执行时都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接和方法返回地址等信息。如果线程请求的栈深度大于虚拟机允许的深度,会抛出 StackOverflowError。
- 本地方法栈(Native Method Stack):它与 JVM 栈的作用类似,但是为执行 native 方法(即由非 Java 语言编写的方法)服务。在 HotSpot 虚拟机中,JVM 栈和本地方法栈通常合二为一。
- 程序计数器(Program Counter Register):这是一块非常小的内存空间,每个线程也都有一个独立的程序计数器,用于记录当前线程正在执行的字节码指令的地址。如果线程正在执行的是 native 方法,则计数器的值为空。这是 JVM 中唯一一个不会发生 OutOfMemoryError 的内存区域。
线程共享的内存区域包括:
- 堆(Heap):这是 JVM 中最大的一块内存区域,几乎所有的对象实例和数组都在这里分配内存。堆是垃圾收集器管理的主要区域。从内存回收的角度来看,堆可以进一步细分为新生代(包括 Eden 区和两个 Survivor 区)和老年代。如果堆中没有足够的内存来分配新的对象实例,则会抛出 OutOfMemoryError。
- 方法区(Method Area) / 元空间(Metaspace):用于存储类的结构信息,例如类的字段、方法、构造函数等,以及运行时常量池。在 Java 8 之前,方法区的实现是永久代(PermGen),它位于 JVM 内存中,大小固定。Java 8 及以后,永久代被元空间(Metaspace) 取代,元空间使用的是本地内存,其大小只受限于操作系统的可用内存。如果方法区或元空间没有足够的内存来加载新的类信息,则会抛出 OutOfMemoryError。
🔥⭐Q: 为什么 JDK 1.8 要移除永久代,而使用元空间?元空间相比永久代有哪些优势?
思考过程:
这个问题考察对 移除永久代并使用元空间的原因和优势的理解。需要从永久代大小固定、容易发生 OOM、垃圾回收效率低等方面进行解释。
- 永久代大小固定,易 OOM: 尤其在类加载频繁的应用中。
- 元空间使用本地内存: 大小受限于操作系统可用内存,OOM 风险降低。
- 永久代垃圾回收效率低: 与老年代捆绑,易触发 Full GC。
- 元空间垃圾回收优化: 可以更灵活地参与 Minor GC 和 Major GC,降低 Full GC 频率。
回答提问:
好的面试官,JDK 1.8 移除永久代并使用元空间主要是为了解决永久代自身固有的缺陷,并提升 JVM 在方法区内存管理上的效率和灵活性。主要有以下几个优势:
首先,永久代的大小是固定的,虽然可以通过参数配置,但在 JVM 启动时就基本确定了。在类加载非常频繁的应用中,例如使用了大量的动态代理、反射或者需要进行代码热部署的场景,永久代很容易发生 OutOfMemoryError: PermGen space
错误,成为系统瓶颈。而元空间使用本地内存,其大小只受限于操作系统的可用内存,虽然仍然可能溢出,但是比永久代出现的几率要小很多,能够加载更多的类元数据。
其次,永久代的垃圾回收效率相对较低。永久代通常与老年代的垃圾回收是捆绑在一起的,更容易触发 Full GC,导致较长的停顿时间,影响应用性能。而元空间使用本地内存后,其垃圾回收机制也得到了优化,可以更灵活地参与到 Minor GC 和 Major GC 中,降低了 Full GC 的频率,提升了整体的垃圾回收效率。
总的来说,使用元空间使得 JVM 在方法区内存管理上更加灵活和高效,降低了内存溢出的风险,并提升了垃圾回收的性能。
🔥🌟Q: 听说过 Java 的 AOT(Ahead-Of-Time)编译吗?它和 JIT 有什么不同?
思考过程:
这个问题考察对 AOT 编译器的理解以及与 JIT 的区别。需要说明 AOT 是在程序运行前就将字节码编译成机器码的技术,并对比它与 JIT 在编译时机和优缺点上的差异。
- 定义: 预先编译器,在程序运行前将字节码编译成机器码。
- 编译时机: AOT 在运行前编译,JIT 在运行时编译。
- AOT 的优点: 减少运行时编译开销,提高启动速度。
- AOT 的缺点: 可能增加编译时间和安装包大小,且优化可能不如 JIT 动态分析。
- JIT 的优点: 可以根据运行时信息进行更优化的编译。
- JIT 的缺点: 存在运行时编译开销。
回答提问:
好的面试官,是的,我知道 AOT(Ahead-Of-Time)编译,它和 JIT(Just-In-Time)编译都是将 Java 字节码转换为机器码的技术,但它们的关键区别在于编译发生的时机。
JIT 编译器是在 Java 程序运行的时候,当发现某些代码成为热点时才进行编译的,属于动态编译。
而 AOT 编译器则是在 Java 程序运行之前,就将所有的(或者部分)字节码预先编译成目标机器的机器码,属于静态编译。
AOT 编译的主要优点是可以减少程序运行时进行编译的开销,从而加快程序的启动速度。因为代码在运行前就已经被编译成本地代码了。
与 JIT 相比,AOT 的缺点可能在于编译时间会更长,并且编译后的安装包可能会更大。此外,AOT 编译可能无法像 JIT 那样根据程序运行时的具体情况进行更细致的优化。
🔥🌟Q: 能对比一下 JVM 中的堆(Heap)和栈(Stack)的区别吗?
思考过程:
这个问题考察对 JVM 堆和栈内存区域关键差异的理解。需要从线程共享性、物理内存分配、存储内容和生命周期等方面进行对比。
- 线程共享性: 堆是线程共享,栈是线程私有。
- 物理内存分配: 堆不连续,栈连续。
- 存储内容: 栈存储局部变量、方法调用上下文,堆存储对象实例和数组。
- 生命周期: 栈中数据与方法生命周期一致,堆中对象由 GC 回收。
回答提问:
好的面试官,JVM 中的堆(Heap)和栈(Stack)是两个非常重要的内存区域,它们之间有以下主要区别:
首先是线程共享性。堆是线程共享的,JVM 中只有一个堆,所有的线程都可以访问堆中的数据,因此在多线程环境下需要考虑线程安全问题。而栈是线程私有的,每个线程在创建时都会拥有自己的栈,栈中的数据只能被当前线程访问。
其次,从物理内存空间分配来看,堆的内存分配通常是不连续的,因此可能会产生内存碎片,导致分配速度相对较慢。而栈的内存分配是连续的,分配和释放速度相对较快。
在存储内容方面,栈主要用于存储方法执行过程中的上下文信息,比如局部变量、操作数栈、方法返回地址等。这些数据与方法的生命周期一致,方法执行完毕后,栈帧会弹出,数据也会自动释放。堆则主要用于存储对象实例和数组,这些数据通过 new
关键字创建,生命周期是不确定的,由垃圾回收器(GC)负责回收。
最后,从生命周期来看,栈中的数据随着方法的执行结束而销毁,具有确定的生命周期。而堆中对象的生命周期由垃圾回收器决定,只有当对象不再被引用时才会被回收。
🔥🌟Q: 你知道什么是内存溢出(Out Of Memory)和内存泄漏(Memory Leak)吗?它们有什么区别?
思考过程:
这个问题考察对 内存溢出和内存泄漏的概念及其区别的理解。需要说明内存溢出是申请不到内存,内存泄漏是本该释放的内存没有释放。
- 内存溢出 (OOM): 程序申请内存超出系统或 JVM 能提供的上限。
- 常见 OOM 场景: 堆溢出、元空间溢出、本地内存溢出、栈溢出。
- 内存泄漏 (Memory Leak): 已分配的内存无法被回收,导致可用内存逐渐减少。
- 常见内存泄漏场景: 资源未正确释放、监听器未取消注册、长生命周期对象持有短生命周期对象引用。
- 区别: 溢出是“不够用”,泄漏是“用完不还”。
回答提问:
好的面试官,内存溢出(Out Of Memory) 指的是程序在申请内存时,系统或 JVM 无法提供足够的连续内存空间来满足其需求,导致程序无法继续运行的一种错误状态。简单来说,就是内存不够用了。常见的内存溢出场景包括堆内存溢出(OOM: Heap Space)、元空间溢出(OOM: Metaspace)、本地内存溢出(OOM: Native memory)以及栈溢出(StackOverflowError)。
内存泄漏(Memory Leak) 则是指程序在动态分配内存后,由于代码缺陷或设计问题,导致一部分已分配的内存无法被程序自身或垃圾回收机制回收,从而造成可用的内存逐渐减少的现象。形象地说,就是该释放的内存没有被释放掉。常见的内存泄漏场景包括资源未正确释放(如文件流、数据库连接)、监听器或回调未取消注册以及长生命周期对象持有短生命周期对象引用等。
它们的主要区别在于,内存溢出是程序需要的内存超出了系统能提供的上限,导致程序崩溃;而内存泄漏是程序本应该释放的内存没有释放,长期积累会导致可用内存越来越少,最终可能也会导致内存溢出。可以说,内存泄漏是内存溢出的一个常见原因。
🔥🌟Q: 你对 Java 里的对象头了解吗?或者说,Java 里的对象在虚拟机里面是怎么存储的?(结合JUC中synchronized来看理解更深)
思考过程:
这个问题考察对 Java 对象内存布局的理解。需要说明对象头、实例数据和对齐填充三个部分,并重点介绍对象头的组成:Mark Word、指向类元数据的指针以及数组长度(如果是数组)。
- 对象内存布局: 对象头、实例数据、对齐填充。
- 对象头组成:Mark Word:存储运行时数据(哈希码、GC 标记、锁状态等)。指向类元数据的指针(Klass Pointer):指向方法区/元空间中的类元数据。数组长度(数组对象特有)。
- 实例数据: 对象中定义的各种字段的值。
- 对齐填充: 为了满足内存对齐要求而添加的字节。
- 内存对齐的作用: 平台原因,性能原因。
回答提问:
好的面试官,Java 对象的内存布局主要由三部分构成:对象头(Header)、实例数据(Instance Data) 和 对齐填充字节(Padding Bytes)。其中,对象头是最为关键的部分。
- 对象头(Header):它主要由以下几部分组成:Mark Word:用于存储对象自身的运行时数据,比如对象的哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等。Mark Word 的内容会根据对象所处的状态而发生变化。例如,在无锁状态下,它会存储对象的哈希码和分代年龄;当对象被 Monitor 锁锁定时,它会变为指向 Monitor 对象的指针。指向类元数据的指针(Klass Pointer):这是一个指向方法区(或元空间)中该对象的类元数据的指针。通过这个指针,JVM 可以确定对象是哪个类的实例。在 HotSpot 虚拟机中,这个指针通常指向方法区中的 Klass 对象。如果开启了指针压缩,这个指针可能会被压缩以节省内存空间。数组长度(Array Length):如果对象是一个数组,那么对象头还会包含一个用于存储数组长度的部分。普通对象没有这部分。
- 实例数据(Instance Data):这部分存储的是对象在代码中定义的各种字段的值,包括从父类继承下来的字段。这些字段会按照一定的顺序在内存中排列,具体的排列顺序可能会受到 JVM 实现和字段类型的影响。
- 对齐填充字节(Padding Bytes):为了满足内存对齐的要求(通常是 8 字节对齐),JVM 可能会在对象的末尾添加一些填充字节。内存对齐主要是为了提高 CPU 访问内存的效率。某些硬件平台只能在特定的内存地址访问特定类型的数据,进行内存对齐可以避免跨越多个缓存行,从而提高性能。
🔥🌟Q: 你能说说 JVM 的主要组成部分吗?
思考过程:
这个问题考察对 JVM 架构的理解。需要列出 JVM 的核心组件,并简要说明它们的作用。
- 类加载器子系统 (ClassLoader): 负责加载 .class 文件到内存中。
- 运行时数据区 (Runtime Data Area): JVM 在运行时使用的内存区域,包括堆、栈、方法区等。
- 执行引擎 (Execution Engine): 负责执行字节码,将其翻译成机器码。
- 本地方法接口 (Native Interface): 允许 JVM 调用本地代码(通常是 C/C++ 编写的)。
回答提问:
好的面试官,JVM 主要由以下几个核心部分组成:
首先是 类加载器子系统 (ClassLoader),它的主要职责是根据特定的策略将编译好的 .class
文件加载到 JVM 的内存中。
其次是 运行时数据区 (Runtime Data Area),这是 JVM 在运行程序时使用的内存区域,它会划分成不同的部分,比如存储对象实例的堆,存储方法调用栈的 Java 虚拟机栈,存储类元信息的方法区等等。
然后是 执行引擎 (Execution Engine),它的作用是执行被加载到内存中的字节码。由于字节码不能直接在操作系统上运行,执行引擎会负责将字节码翻译成底层操作系统可以理解的机器码并执行。
最后是 本地方法接口 (Native Interface),它允许 JVM 调用使用其他编程语言(比如 C 或 C++)编写的本地方法。这使得 Java 可以利用底层操作系统的功能。
🔥💡Q: 你知道 Java 中的 JIT(Just-In-Time)编译器是什么吗?它的作用是什么?
思考过程:
这个问题考察对 JIT 编译器的理解。需要说明 JIT 是在运行时将字节码编译成机器码的技术,以及它提升性能的作用。
- 定义: 即时编译器,在运行时将字节码编译成机器码。
- 触发时机: 发现热点代码(频繁执行的代码段)。
- 作用: 减少解释执行的开销,提高程序性能,使其接近本地代码的性能。
回答提问:
好的面试官,JIT(Just-In-Time)编译器,也就是即时编译器,是 Java 虚拟机(JVM)中的一个重要组成部分。它的主要作用是在程序运行的时候,将那些被频繁执行的代码段(也就是我们常说的“热点代码”) 编译成本地机器码。
这样做的好处是可以减少解释执行的开销,因为 JVM 最初对字节码是采用解释执行的方式,逐条翻译执行,这会比较耗时。当 JIT 编译器发现某段代码被多次执行后,就会将其编译成可以直接在底层硬件上运行的机器码,这样下次再执行这段代码时,就无需再进行解释,从而显著提高程序的运行性能,使得 Java 代码的执行效率可以接近甚至在某些情况下超过本地代码的性能。
🔥💡Q: 你了解 TLAB(Thread-Local Allocation Buffer)吗?
思考过程:
这个问题考察对 TLAB 的理解,需要说明 TLAB 的概念、作用以及工作原理。
- 定义: JVM 为每个线程分配的一小块私有堆内存区域。
- 作用: 加速对象分配,避免多线程竞争共享堆内存时的同步开销。
- 工作原理: 线程优先从自己的 TLAB 分配内存,TLAB 用完再向 Eden 区申请或直接从 Eden 区分配,大对象不分配在 TLAB 中。
回答提问:
好的面试官,我对 TLAB(Thread-Local Allocation Buffer) 是有所了解的。它实际上是 JVM 为了加速对象分配而为每个线程分配的一小块私有内存区域,这块内存是位于堆中的。
由于对象分配是 Java 程序中最常见的操作之一,而堆内存是所有线程共享的,因此当多个线程同时在堆上分配对象时,可能会发生线程竞争,这会导致需要进行同步操作,从而降低了分配效率。
TLAB 的引入就是为了解决这个问题。JVM 会为每个线程在 Eden 区分配一小块独立的内存区域,也就是 TLAB。这样,每个线程就可以优先从自己的 TLAB 中分配对象,从而避免了多线程竞争共享堆内存时的同步开销,提高了对象分配的速度。
TLAB 的工作原理大致是这样的:每个线程在执行过程中,会首先尝试从自己的 TLAB 中分配内存。当 TLAB 中的内存耗尽时,线程会重新向 Eden 区申请一个新的 TLAB,或者在某些情况下,直接从 Eden 区分配内存。需要注意的是,对于体积比较大的对象(大对象),JVM 通常不会在 TLAB 中进行分配,而是直接在 Eden 区进行分配。
类加载相关(主要考察常见的类加载器、类加载过程、双亲委派模型以及如何打破双亲委派模型)
🔥⭐Q:能简单聊聊什么是类加载吗?它的整个过程是怎样的?
思考过程: 这个问题是考察对类加载机制最基础的理解,属于面试中的重点。可以从以下几个方面进行思考:
- 定义: 类加载的目的是什么?它在 JVM 中扮演什么角色?
- 输入: 类加载的输入是什么?(Class 文件)
- 输出: 类加载的输出是什么?(内存中的 Class 对象)
- 核心步骤: 类加载包含哪几个主要阶段?每个阶段的主要任务是什么?需要记住每个阶段的名称和关键操作。
- 重点阶段: 哪些阶段是面试中经常被问到的?(加载、验证、准备、解析、初始化)
- 联系: 类加载与 JVM 运行时数据区的关系是什么?
回答提问: 好的面试官,关于类加载,我的理解是这样的:类加载本质上是为了让 JVM 能够使用我们的类。它会将 Class 文件中的二进制数据读取到内存中,具体来说是方法区,并且会转换成 JVM 能够识别的格式。同时,在堆内存中会创建一个代表这个类的 Class 对象,这个对象就成为了我们访问方法区中类信息的入口。
整个类加载的过程主要包含五个阶段,可以概括为:加载 (Loading)、验证 (Verification)、准备 (Preparation)、解析 (Resolution) 和初始化 (Initialization)。
- 加载阶段主要是查找并加载类的二进制数据到方法区。
- 验证阶段是为了确保加载的二进制数据符合 JVM 规范,保证程序的安全性。
- 准备阶段会为类的静态变量分配内存,并设置默认的初始值,比如 int 类型会初始化为 0,引用类型会初始化为 null。
- 解析阶段则是将 Class 文件常量池中的符号引用替换为直接指向内存地址的直接引用。
- 最后的初始化阶段会执行类构造器 <clinit>() 方法,这个方法会收集类中所有静态变量的赋值动作和静态代码块中的语句并执行。需要注意的是,父类的 <clinit>() 方法会在子类之前执行。
🔥⭐Q:你能列举一些常见的类加载器吗?
思考过程:
这个问题考察对 JVM 提供的默认类加载器的了解。需要记住常见的类加载器及其职责。
回答提问:
当然,JVM 中常见的类加载器主要有以下几种:
- 启动类加载器 (Bootstrap ClassLoader): 这是最顶层的类加载器,它负责加载 JVM 核心类库,例如 java.lang.* 等,这些类库位于 <JAVA_HOME>/jre/lib/rt.jar 等目录中。它是用 C++ 实现的,是 JVM 自身的一部分。
- 扩展类加载器 (Extension ClassLoader): 它是由启动类加载器加载的,负责加载扩展目录中的类库,这些类库通常位于 <JAVA_HOME>/jre/lib/ext 目录中。
- 应用程序类加载器 (Application ClassLoader) / 系统类加载器 (System ClassLoader): 它负责加载应用程序 ClassPath 下的所有类库,是我们平时开发中最常用的类加载器。默认情况下,我
全部评论
(2) 回帖