本篇是关于 JVM 内存的详细分析。网上有很多关于 JVM 内存结构的分析以及图片,但是由于不是一手的资料亦或是人云亦云导致有很错误,造成了很多误解;并且,这里可能最容易混淆的是一边是 JVM Specification 的定义,一边是 Hotspot JVM 的实际实现,有时候人们一些部分说的是 JVM Specification,一部分说的是 Hotspot 实现,给人一种割裂感与误解。本篇主要从 Hotspot 实现出发,以 Linux x86 环境为主,紧密贴合 JVM 源码并且辅以各种 JVM 工具验证帮助大家理解 JVM 内存的结构。但是,本篇仅限于对于这些内存的用途,使用限制,相关参数的分析,有些地方可能比较深入,有些地方可能需要结合本身用这块内存涉及的 JVM 模块去说,会放在另一系列文章详细描述。最后,洗稿抄袭狗不得 house
本篇全篇目录(以及涉及的 JVM 参数):
-
从 Native Memory Tracking 说起(全网最硬核 JVM 内存解析 - 1.从 Native Memory Tracking 说起开始)
- Native Memory Tracking 的开启
- Native Memory Tracking 的使用(涉及 JVM 参数:
NativeMemoryTracking
) - Native Memory Tracking 的 summary 信息每部分含义
- Native Memory Tracking 的 summary 信息的持续监控
- 为何 Native Memory Tracking 中申请的内存分为 reserved 和 committed
-
JVM 内存申请与使用流程(全网最硬核 JVM 内存解析 - 2.JVM 内存申请与使用流程开始)
-
Linux 下内存管理模型简述
-
JVM commit 的内存与实际占用内存的差异
- JVM commit 的内存与实际占用内存的差异
-
大页分配 UseLargePages(全网最硬核 JVM 内存解析 - 3.大页分配 UseLargePages开始)
- Linux 大页分配方式 - Huge Translation Lookaside Buffer Page (hugetlbfs)
- Linux 大页分配方式 - Transparent Huge Pages (THP)
- JVM 大页分配相关参数与机制(涉及 JVM 参数:
UseLargePages
,UseHugeTLBFS
,UseSHM
,UseTransparentHugePages
,LargePageSizeInBytes
)
-
-
Java 堆内存相关设计(全网最硬核 JVM 内存解析 - 4.Java 堆内存大小的确认开始)
-
通用初始化与扩展流程
-
直接指定三个指标的方式(涉及 JVM 参数:
MaxHeapSize
,MinHeapSize
,InitialHeapSize
,Xmx
,Xms
) -
不手动指定三个指标的情况下,这三个指标(MinHeapSize,MaxHeapSize,InitialHeapSize)是如何计算的
-
压缩对象指针相关机制(涉及 JVM 参数:
UseCompressedOops
)(全网最硬核 JVM 内存解析 - 5.压缩对象指针相关机制开始)- 压缩对象指针存在的意义(涉及 JVM 参数:
ObjectAlignmentInBytes
) - 压缩对象指针与压缩类指针的关系演进(涉及 JVM 参数:
UseCompressedOops
,UseCompressedClassPointers
) - 压缩对象指针的不同模式与寻址优化机制(涉及 JVM 参数:
ObjectAlignmentInBytes
,HeapBaseMinAddress
)
- 压缩对象指针存在的意义(涉及 JVM 参数:
-
为何预留第 0 页,压缩对象指针 null 判断擦除的实现(涉及 JVM 参数:
HeapBaseMinAddress
) -
结合压缩对象指针与前面提到的堆内存限制的初始化的关系(涉及 JVM 参数:
HeapBaseMinAddress
,ObjectAlignmentInBytes
,MinHeapSize
,MaxHeapSize
,InitialHeapSize
) -
使用 jol + jhsdb + JVM 日志查看压缩对象指针与 Java 堆验证我们前面的结论
- 验证
32-bit
压缩指针模式 - 验证
Zero based
压缩指针模式 - 验证
Non-zero disjoint
压缩指针模式 - 验证
Non-zero based
压缩指针模式
- 验证
-
堆大小的动态伸缩(涉及 JVM 参数:
MinHeapFreeRatio
,MaxHeapFreeRatio
,MinHeapDeltaBytes
)(全网最硬核 JVM 内存解析 - 6.其他 Java 堆内存相关的特殊机制开始) -
适用于长期运行并且尽量将所有可用内存被堆使用的 JVM 参数 AggressiveHeap
-
JVM 参数 AlwaysPreTouch 的作用
-
JVM 参数 UseContainerSupport - JVM 如何感知到容器内存限制
-
JVM 参数 SoftMaxHeapSize - 用于平滑迁移更耗内存的 GC 使用
-
-
JVM 元空间设计(全网最硬核 JVM 内存解析 - 7.元空间存储的元数据开始)
-
什么是元数据,为什么需要元数据
-
什么时候用到元空间,元空间保存什么
- 什么时候用到元空间,以及释放时机
- 元空间保存什么
-
元空间的核心概念与设计(全网最硬核 JVM 内存解析 - 8.元空间的核心概念与设计开始)
-
元空间的整体配置以及相关参数(涉及 JVM 参数:
MetaspaceSize
,MaxMetaspaceSize
,MinMetaspaceExpansion
,MaxMetaspaceExpansion
,MaxMetaspaceFreeRatio
,MinMetaspaceFreeRatio
,UseCompressedClassPointers
,CompressedClassSpaceSize
,CompressedClassSpaceBaseAddress
,MetaspaceReclaimPolicy
) -
元空间上下文
MetaspaceContext
-
虚拟内存空间节点列表
VirtualSpaceList
-
虚拟内存空间节点
VirtualSpaceNode
与CompressedClassSpaceSize
-
MetaChunk
ChunkHeaderPool
池化MetaChunk
对象ChunkManager
管理空闲的MetaChunk
-
类加载的入口
SystemDictionary
与保留所有ClassLoaderData
的ClassLoaderDataGraph
-
每个类加载器私有的
ClassLoaderData
以及ClassLoaderMetaspace
-
管理正在使用的
MetaChunk
的MetaspaceArena
-
元空间内存分配流程(全网最硬核 JVM 内存解析 - 9.元空间内存分配流程开始)
- 类加载器到
MetaSpaceArena
的流程 - 从
MetaChunkArena
普通分配 - 整体流程 - 从
MetaChunkArena
普通分配 -FreeBlocks
回收老的current chunk
与用于后续分配的流程 - 从
MetaChunkArena
普通分配 - 尝试从FreeBlocks
分配 - 从
MetaChunkArena
普通分配 - 尝试扩容current chunk
- 从
MetaChunkArena
普通分配 - 从ChunkManager
分配新的MetaChunk
- 从
MetaChunkArena
普通分配 - 从ChunkManager
分配新的MetaChunk
- 从VirtualSpaceList
申请新的RootMetaChunk
- 从
MetaChunkArena
普通分配 - 从ChunkManager
分配新的MetaChunk
- 将RootMetaChunk
切割成为需要的MetaChunk
MetaChunk
回收 - 不同情况下,MetaChunk
如何放入FreeChunkListVector
- 类加载器到
-
ClassLoaderData
回收
-
-
元空间分配与回收流程举例(全网最硬核 JVM 内存解析 - 10.元空间分配与回收流程举例开始)
- 首先类加载器 1 需要分配 1023 字节大小的内存,属于类空间
- 然后类加载器 1 还需要分配 1023 字节大小的内存,属于类空间
- 然后类加载器 1 需要分配 264 KB 大小的内存,属于类空间
- 然后类加载器 1 需要分配 2 MB 大小的内存,属于类空间
- 然后类加载器 1 需要分配 128KB 大小的内存,属于类空间
- 新来一个类加载器 2,需要分配 1023 Bytes 大小的内存,属于类空间
- 然后类加载器 1 被 GC 回收掉
- 然后类加载器 2 需要分配 1 MB 大小的内存,属于类空间
-
元空间大小限制与动态伸缩(全网最硬核 JVM 内存解析 - 11.元空间分配与回收流程举例开始)
CommitLimiter
的限制元空间可以 commit 的内存大小以及限制元空间占用达到多少就开始尝试 GC- 每次 GC 之后,也会尝试重新计算
_capacity_until_GC
-
jcmd VM.metaspace
元空间说明、元空间相关 JVM 日志以及元空间 JFR 事件详解(全网最硬核 JVM 内存解析 - 12.元空间各种监控手段开始)-
jcmd <pid> VM.metaspace
元空间说明 -
元空间相关 JVM 日志
-
元空间 JFR 事件详解
jdk.MetaspaceSummary
元空间定时统计事件jdk.MetaspaceAllocationFailure
元空间分配失败事件jdk.MetaspaceOOM
元空间 OOM 事件jdk.MetaspaceGCThreshold
元空间 GC 阈值变化事件jdk.MetaspaceChunkFreeListSummary
元空间 Chunk FreeList 统计事件
-
-
-
JVM 线程内存设计(重点研究 Java 线程)(全网最硬核 JVM 内存解析 - 13.JVM 线程内存设计开始)
-
JVM 中有哪几种线程,对应线程栈相关的参数是什么(涉及 JVM 参数:
ThreadStackSize
,VMThreadStackSize
,CompilerThreadStackSize
,StackYellowPages
,StackRedPages
,StackShadowPages
,StackReservedPages
,RestrictReservedStack
) -
Java 线程栈内存的结构
-
Java 线程如何抛出的 StackOverflowError
- 解释执行与编译执行时候的判断(x86为例)
- 一个 Java 线程 Xss 最小能指定多大
-
4. JVM 元空间设计
4.3. 元空间的核心概念与设计
4.3.9. 元空间内存分配流程
我们过一下元空间内存分配流程,我们会忽略一些 GC 相关的还有并发安全的细节,否则涉及的概念太多,一下说不过来,这些细节,会在以后的系列中详细提到。
4.3.9.1. 类加载器到 MetaSpaceArena
的流程
当类加载器加载类的时候,需要从对应的 ClassLoaderMetaspace
分配元空间进行存储。这个过程大概是:
图中有蓝色填充的方块是我们要重点分析的流程,我们先从从 MetaChunkArena 普通分配开始分析,尝试 GC 以及扩容元空间用于分配会涉及到元空间大小限制以及 GC 界限的概念,我们后面分析。这里对应的源码是:https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace.cpp#L899
整个流程如下:
- 首先,验证要分配的内存小于最大
MetaChunk
大小,即RootMetaChunk
大小,即ChunkLevel
= 0 的大小,即4MB
- 然后,走普通分配流程,判断当前分配的数据类型是类元空间的还是数据元空间的,分别到类元空间的
MetaSpaceArena
或者数据源空间MetaSpaceArena
进行分配。这是下一节我们要详细分析的。 - 如果普通分配失败,那么会触发
jdk.MetaspaceAllocationFailure
这个 JFR 事件,大家可以监控这个事件,去调整元空间大小减少由于元空间不足触发的 GC。触发之后,抄袭狗死全家 - 之后,尝试 GC,以及增大元空间的 GC 界限(元空间有最大大小限制,但是还有动态计算的 GC 界限,超过 GC 界限的话,第二步的普通分配也会失败)用于分配。这个流程我们后面会详细分析
- 最后,如果这样还是分配失败,那么就会抛出大名鼎鼎的
java.lang.OutOfMemoryError
, 触发jdk.MetaspaceOOM
这个 JFR 事件,这个我们也会详细分析。
我们先分析第二步的普通分配流程,其他的需要后续我们分析元空间大小限制的时候详细分析。
4.3.9.2. 从 MetaChunkArena
普通分配 - 整体流程
从 MetaChunkArena
普通分配的流程并不太复杂:
我们前面讲过 MetaspaceArena
的结构,如下所示:
对应的源码是 https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArena.cpp#L222
,结合流程图,我们可以整理出一个简单的分配思路:
-
首先,尝试从
FreeBlocks
中分配(第一次分配,肯定FreeBlocks
里面没有可以分配的,直接进入下一分支),如果分配成功,直接返回 -
然后,尝试从
current chunk
分配(第一次分配肯定current chunk
为NULL
,直接进入下一分支):current chunk
如果有足够的空间,并且这些空间是 committed 的或者可以 commit 成功,那么从current chunk
分配。current chunk
如果没有足够的空间,尝试current chunk
扩容,如果扩容成功,并且这些空间是 committed 的或者可以 commit 成功,那么从current chunk
分配。- 如果扩容失败,或者 commit 失败,直接进入下一分支
-
前面分配失败,尝试从
ChunkManager
中申请一个新的MetaChunk
。 -
如果申请成功,则加入当前的
MetaChunkList
中,将current chunk
指向新的MetaChunk
,老的current chunk
的剩余空间回收到FreeBlocks
中,然后从新的current chunk
分配。
我们接下来分析 FreeBlocks
回收老的 current chunk
与用于后续分配的流程
4.3.9.3. 从 MetaChunkArena
普通分配 - FreeBlocks
回收老的 current chunk
与用于后续分配的流程
首先我们前面提到过:Freeblocks
包含一个 BinList32
和一个 BlockTree
。大小大于 33 字节的进入 BlockTree
,否则进入 BinList32
。
BinList32
类似于 FreeChunkListVector
,是一个链表的数组,同样大小的内存在同一数组下标的链表。
BlockTree
是一个在 Binary Search Tree (BST)的基础上,同样内存的节点在二叉树节点的后面形成链表的数据结构。
回收的流程十分简单,就是判断下 current chunk
的剩余空间,根据大小放到不同的数据结构:
- 计算
MetaChunk
剩余的committed
的空间(很简单,直接用committed
的位置减去已分配到的位置) - 判断剩余空间是否大于
FreeBlocks
最小内存块大小(即BinList32
最小大小即2KB
):对待抄袭狗不必手软 - 从
MetaChunk
分配剩余的committed
的空间,放入FreeBlocks
中 - 内存大于
BinList32
最大内存块大小即33KB
,放入BlockTree
,否则放入BinList32
4.3.9.4. 从 MetaChunkArena
普通分配 - 尝试从 FreeBlocks
分配
尝试从 FreeBlocks
分配即从其中的 BinList32
和 BlockTree
寻找是否有合适的内存,流程是:
- 首先判断,要分配的内存大小是否大于
BinList32
最大内存块大小即33KB
:如果大于,就从BlockTree
查找不小于内存大小的最接近的内存块;如果不大于,就从BinList32
查找是否有对应大小的内存块。 - 如果找到了,计算
waste
,waste = 内存块大小 - 要分配的内存大小
。 - 判断
waste
大于FreeBlocks
最小内存块大小(即BinList32
最小大小即2KB
)。如果大于,则要回收,和前面回收MetaChunk
的流程一样将剩余的内存放回FreeBlocks
。
4.3.9.5. 从 MetaChunkArena
普通分配 - 尝试扩容 current chunk
enlarge_chunks_in_place
是否是true
,不是的话直接结束,不过前面我们说过,目前JVM
是代码里写死的true
- 判断是否
current chunk
已经是RootMetaChunk
(代表已经不能扩容了),如果是,直接结束 current chunk
已使用大小加上要分配的内存大小是否大于RootMetaChunk
的大小即4MB
(代表已经不能扩容了),如果是,直接结束- 找到大于
current chunk
已使用大小,加上要分配的内存大小的最接近的ChunkLevel
(记为new_level
) - 判断
new_level
是否小于current chunk
的ChunkLevel
减 1,代表要扩容到的大小大于原始大小的 2 倍以上(不允许一下子扩容两倍以上),如果是,直接结束 current chunk
是否是leader
(这个概念后面分析到使用ChunkManager
分配新的MetaChunk
会提到),只有leader
可以扩容,如果不是,直接结束(xigao 必死)- 判断扩容策略中申请下一个
MetaChunk
的ChunkLevel
是否大于current chunk
的(代表新申请的比当前的小),如果是,也直接结束。我们这里强调下为啥扩容策略(ArenaGrowthPolicy
)中申请下一个MetaChunk
的ChunkLevel
大于current chunk
(代表新申请的比当前的小)的话,我们就不扩容了。前面我们列出了各种类型的ClassLoader
的不同空间的扩容策略,例如DelegatingClassLoader
的ClassLoaderMetaspace
数据元空间的MetaspaceArena
的ArenaGrowthPolicy
:MetachunkList
的第一个MetaChunk
大小为2K
,之后每个新MetaChunk
都是1K
。假设current chunk
是第一个,这里下一个MetaChunk
的ChunkLevel
是1K
对应的ChunkLevel
,大于current chunk
当前的ChunkLevel
,所以优先申请新的,而不是扩容。之后到第二个之后,由于之后每个新的MetaChunk
都是1K
,就会尝试扩容而不是申请新的了。 - 使用
ChunkManager
尝试扩容current chunk
到new_level
。具体扩容流程,后面会分析。
4.3.9.6. 从 MetaChunkArena
普通分配 - 从 ChunkManager
分配新的 MetaChunk
回顾下 ChunkManager
结构:
从 ChunkManager
分配新的 MetaChunk
,首先会从 FreeChunkListVector
尝试搜索有没有合适的。FreeChunkListVector
如我们之前所述,是一个以 ChunkLevel
为下标的数组,每个数组都是一个 MetaChunk
的链表。commit
多的 MetaChunk
放在链表开头,完全没有 commit
的放在链表末尾。
- 计算两个值:
max_level = 大于当前申请内存大小最接近的 ChunkLevel (即新的 MetaChunk 最小多大)
,preferred_level = "根据扩容策略(ArenaGrowthPolicy)下一个 MetaChunk 多大" 与 "max_level" 中小的那个值(也就是更大的 MetaChunk 大小)
- 优先搜索并使用
FreeChunkListVector
中那些已经commit
足够内存的MetaChunk
- 正序遍历(即
ChunkLevel
从小到大,大小从大到小)ChunkManager
的FreeChunkListVector
里面的数组 (从preferred_level
到max_level
与preferred_level
+ 2 中比较小的值,即最多搜索 3 个ChunkLevel
,根据前面的分析我们知道ChunkLevel
就是数组下标),寻找对应的MetaChunk
链表,正序遍历每个链表(我们前面提到过,commit
多的MetaChunk
放在开头),直到找到commit
大小大于申请内存大小的(chaoxi 死的更惨) - 逆序遍历(即
ChunkLevel
从大到小,大小从小到大)ChunkManager
的FreeChunkListVector
里面的数组 (从preferred_level
到最大的ChunkLevel
,即RootMetaChunk
的大小,即 4MB),寻找对应的MetaChunk
链表,正序遍历每个链表(我们前面提到过,commit
多的MetaChunk
放在开头),直到找到commit
大小大于申请内存大小的 - 正序遍历(即
ChunkLevel
从小到大,大小从大到小)ChunkManager
的FreeChunkListVector
里面的数组 (从preferred_level
到max_level
),寻找对应的MetaChunk
链表,正序遍历每个链表(我们前面提到过,commit
多的MetaChunk
放在开头),直到找到commit
大小大于申请内存大小的 - 如果搜索不到已经
commit
足够内存的MetaChunk
,就退而求其次,寻找FreeChunkListVector
存在的MetaChunk
- 正序遍历(即
ChunkLevel
从小到大,大小从大到小)ChunkManager
的FreeChunkListVector
里面的数组 (从preferred_level
到max_level
),寻找对应的MetaChunk
链表,正序遍历每个链表,直到找到一个MetaChunk
- 逆序遍历(即
ChunkLevel
从大到小,大小从小到大)ChunkManager
的FreeChunkListVector
里面的数组 (从preferred_level
到最大的ChunkLevel
,即RootMetaChunk
的大小,即 4MB),寻找对应的MetaChunk
链表,正序遍历每个链表,直到找到一个MetaChunk
- 如果前面没有找到合适的,从
VirtualSpaceList
申请新的RootMetaChunk
- 将
RootMetahChunk
分割成需要的ChunkLevel
大小,之后将分割剩余的放入FreeChunkListVector
,这个过程我们接下来会详细分析 - 判断
new_chunks_are_fully_committed
是否为true
,如果为true
则commit
整个MetaChunk
的所有内存,否则commit
要分配的大小。如果commit
失败了(证明可能到达元空间 GC 界限或者元空间大小上限),那么将MetaChunk
退回。
4.3.9.7. 从 MetaChunkArena
普通分配 - 从 ChunkManager
分配新的 MetaChunk
- 从 VirtualSpaceList
申请新的 RootMetaChunk
- 首先判断当前
_first_node
是否有空间分配新的RootMetaChunk
,如果有则从_first_node
上面分配新的RootMetaChunk
- 如果没有,判断是否可以扩展新的
VirtualSpaceNode
(类元空间不可以,数据元空间可以),如果可以则申请Reserve
新的VirtualSpaceNode
作为新的_first_node
,之后从_first_node
上面分配新的RootMetaChunk
4.3.9.8. 从 MetaChunkArena
普通分配 - 从 ChunkManager
分配新的 MetaChunk
- 将 RootMetaChunk
切割成为需要的 MetaChunk
这里的流程如果用流程图容易把人绕晕,我们这里举一个例子,比如我们想要一个 ChunkLevel
为 3 的 MetaChunk
:
将 RootMetaChunk
切割成 ChunkLevel
为 3 的 MetaChunk
的流程:
RootMetaChunk
的ChunkLevel
为 0,对半分成两个ChunkLevel
为 1 的,第一个为leader
,第二个为follower
。- 将上一步的
leader
对半成两个ChunkLevel
为 2 的,第一个为leader
,第二个为follower
。 - 将上一步的
leader
对半成两个ChunkLevel
为 3 的,第一个为leader
,第二个为follower
。 - 将第三步的
leader
返回,用于分配。将第一、二、三步生成的follower
放入FreeChunkListVector
用于前面 4.3.9.6 章节分析的ChunkManager
先从FreeChunkListVector
搜索合适的MetaChunk
分配。
4.3.9.9. MetaChunk
回收 - 不同情况下, MetaChunk
如何放入 FreeChunkListVector
我们前面主要分析的是分配,那么 MetaChunk
如何回收呢?从前面的流程我们很容易推测出来,其实就是放回 FreeChunkListVector
。放回的流程如果用流程图容易把人绕晕,我们还是举例子区分不同情况。其实核心思路就是,放回的时候,尽量将 MetaChunk
向上合并之后放回:
这里我们有两个例子:
- 我们有一个
ChunkLevel
为 3 的MetaChunk
要回收,但是它不是leader
,不能向上合并。只有leader
才会尝试向上合并。这里直接放入FreeChunkListVector
。 - 我们又有一个
ChunkLevel
为 3 的MetaChunk
要回收,它是leader
。它会尝试向上合并。查看它的follower
是否是Free
的。如果是Free
的,他肯定首先在ChunkManager
的FreeChunkListVector
中, 从FreeChunkListVector
取出,与这个leader
合并为一个新的ChunkLevel
为 2。之后,它还是leader
,尝试继续合并,但是它的follower
不是空闲的,就不能继续合并了。在这里停止,放入FreeChunkListVector
。
4.3.10. ClassLoaderData
回收
在 GC 判断一个类加载器可以回收(该类加载器加载的类没有任何对象,该类加载器的对象也没有任何强引用指向它)的时候,不会立刻回收 ClassLoaderData
,而是对应的 ClassLoaderData
的 is_alive()
就会返回 false
。JVM 会定期遍历 ClassLoaderDataGraph
遍历每个 ClassLoaderData
判断 is_alive()
是否是 false
,如果是的话会放入待回收的链表中。之后在不同 GC 的不同阶段,遍历这个链表将 ClassLoaderData
回收掉。
ClassLoaderData
被回收的过程如下所示:
`
ClassLoaderData
会记录所有加载的类与相关的数据(前文提到的 Klass
等等对象),所以它的析构函数中会将这些加载的数据的内存全部释放到它独有的 MetaSpaceArena
的 FreeBlocks
中,这些内存就是通过之前我们分析的流程分配的,由于之前的空间都是从 MetaspaceArena
的 MetaChunkList
中的 MetaChunk
分配的,这样的话这些 MetaChunk
的空间也都不再占用了。当然,也会把前面提到的 ClassLoaderData
独有的数据结构释放掉,还没有利用的 MetaWord
放回 ChunkManager
中。然后,清除掉它私有的 ClassLoadMetaSpace
。根据前文分析我们知道 ClassLoaderMetaspace
在开启压缩类空间的情况下包括一个类元空间的 MetaspaceArena
和一个数据元空间的 MetaspaceArena
。这两个 MetaspaceArena
分别要清理掉。MetaspaceArena
的析构函数会把 FreeBlocks
中的每个 MetaWord
都放回 ChunkManager
,注意这里包含之前 ClassLoaderData
放回的加载类相关数据占用的空间,最后清理掉 FreeBlocks
。(你洗稿的样子真丑。)
微信搜索“干货满满张哈希”关注公众号,加作者微信,每日一刷,轻松提升技术,斩获各种offer
我会经常发一些很好的各种框架的官方社区的新闻视频资料并加上个人翻译字幕到如下地址(也包括上面的公众号),欢迎关注:
- 知乎:https://www.zhihu.com/people/zhxhash
- B 站:https://space.bilibili.com/31359187
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。