类初始化和加载
创建对象
Java 对象创建的核心流程:
- 类加载检查 
- 内存分配 
- 初始化零值 
- 设置对象头 
- 执行构造方法 
内存分配策略和并发控制机制(如 TLAB(Thread-Local Allocation Buffer))直接影响性能
而对象内存布局的设计优化了 GC 效率与访问速度。
对象生命周期
Java 对象的生命周期可概括为:
- 创建阶段 
- 应用阶段(被强引用持有) 
- 不可达阶段(GC Roots 无法访问) 
- 垃圾回收阶段(可能触发 finalize() 方法) 
- 内存回收阶段 
类加载器
类加载器有哪些?
- 启动类加载器:Bootstrap ClassLoader - 用来加载 Java 核心类库,无法被 Java 程序直接引用 
- 如加载存放在 JDK\jre\lib(JDK 代表 JDK 的安装目录,下同)下 
- 或被 -Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库 
 
- 扩展类加载器:Extension ClassLoader - 该加载器由 sun.misc.Launcher$ExtClassLoader 实现 
- 它负责加载 JDK\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.* 开头的类) 
- 开发者可以直接使用扩展类加载器 
 
- 应用程序类加载器:Application ClassLoader - 该类加载器由 sun.misc.Launcher$AppClassLoader 来实现 
- 它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器 
 
- 用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现 
双亲委派模型
双亲委派模型的作用
工作过程
- 如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成 
- 每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中 
- 只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类 
作用
- 避免类的重复加载:通过父类加载器优先加载类,确保同一类在JVM中仅被加载一次,减少内存占用和版本冲突风险 
- 保证核心类库的安全性:防止用户自定义类覆盖JVM核心类(如 - java.lang.String),通过父类加载器优先加载核心类库实现安全隔离
- 提高类加载效率:父类加载器已加载的类可直接复用,减少重复加载的开销 
- 隔离类加载器的命名空间:不同类加载器加载的类处于独立命名空间,避免类冲突(如Tomcat多Web应用场景) 
- 维护类的统一行为:确保核心类(如 - Object)在所有加载器中行为一致,避免因类版本不同导致的逻辑异常
类加载过程
- 类从被加载到虚拟机内存中开始,到卸载出内存为止 
- 它的整个生命周期包括: - 加载(Loading) 
- 验证(Verification) 
- 准备(Preparation) 
- 解析(Resolution) 
- 初始化(Initialization) 
- 使用(Using) 
- 卸载(Unloading) 
 
其中验证、准备、解析 3 个部分统称为连接(Linking)
详见 类加载过程
类加载和双亲委派原则
就是把类加载过程和双亲委派机制加载步骤说明下就行了
垃圾回收
垃圾判断
3种方法
- 引用计数法:个对象维护一个引用计数器,记录当前被引用的次数 
- 可达性分析算法:从 GC Roots 出发,遍历所有可达对象,未被遍历到的对象视为不可达(即垃圾) 
- 引用类型判定法:根据引用类型的不同,决定对象回收的优先级和条件 
详见 对象已死?
垃圾回收算法
- 分代收集算法 
- 标记-清除算法 
- 标记-整理算法 
- 复制算法。部分也表述为标记-复制算法 
详见 垃圾收集算法
标记清除缺点
标记清除算法的缺点是什么?
- 内存碎片化问题 
- 执行效率低:标记和清除阶段均需遍历堆中的所有对象,时间复杂度为 O(n) 
- 无法处理循环引用 
- 不可预测的回收时机:被动触发,标记清除算法通常在全堆内存不足时触发回收,导致回收时机不可预测,可能在高负载时引发性能抖动 
- 内存访问冲突风险:多线程问题,在标记和清除过程中,若与程序线程并发访问内存,可能引发数据不一致或崩溃 
垃圾回收器
分类
- 经典垃圾回收器 - Serial 收集器 - 特点:单线程串行回收,采用 "Stop-The-World" 机制;新生代使用复制算法,老年代(Serial Old)使用标记-整理算法 
- 适用场景:单核 CPU 或小内存客户端应用(如早期桌面程序) 
- 参数: - -XX:+UseSerialGC
 
- ParNew 收集器 - 特点:Serial 的多线程版本,新生代并行回收(复制算法),需与 CMS 搭配使用 
- 适用场景:JDK8 及之前版本的服务器端低延迟场景 
- 参数: - -XX:+UseParNewGC
 
- Parallel Scavenge/Old(PS+PO) - 特点:多线程并行回收,以吞吐量优先(JDK8 默认组合),支持自适应调节堆大小 
- 适用场景:后台批处理、大数据计算等高吞吐场景 
- 参数: - -XX:+UseParallelGC
 
- CMS(Concurrent Mark Sweep) - 特点:并发标记清除(减少停顿时间),老年代使用标记-清除算法;存在内存碎片和浮动垃圾问题 
- 适用场景:Web 服务、订单系统等低延迟场景(JDK14 后已移除) 
- 参数: - -XX:+UseConcMarkSweepGC
 
 
- 现代垃圾回收器 - G1(Garbage-First) - 特点:分区式(Region)、并行与并发结合;采用复制算法和预测性停顿模型,支持大堆内存(6GB+) 
- 优势:平衡吞吐量与延迟,JDK9 后成为默认回收器 
- 参数: - -XX:+UseG1GC
 
- ZGC(Z Garbage Collector) - 特点:超低延迟(停顿 <10ms),支持 TB 级堆内存;通过读屏障和并发压缩实现 
- 适用场景:金融交易、实时系统等对延迟敏感的场景(JDK11+ 支持) 
- 参数: - -XX:+UseZGC
 
- Shenandoah - 特点:通过“颜色指针”和并发整理减少停顿时间,适合大堆且低延迟需求 
- 适用场景:大堆内存、低延迟场景(需手动启用) 
- 参数: - -XX:+UseShenandoahGC
 
- Epsilon GC - 特点:无操作的回收器,仅分配内存不回收;用于性能测试或内存管理完全可控的场景 
- 适用场景:调试、短期任务或内存泄漏检测 
- 参数: - -XX:+UseEpsilonGC
 
 
详见 经典的垃圾收集器
G1回收器
G1 回收器的特色是什么?
- 基于分区的内存管理机制 - 将堆内存划分为多个等大小的 Region(默认约 2048 个),每个 Region 可动态切换为 Eden、Survivor、Old 或 Humongous 区域 
- 支持动态分代调整:年轻代占比通过 - -XX:G1NewSizePercent和- -XX:G1MaxNewSizePercent在 5%~60% 间自动伸缩
- 大对象优化:超过单个 Region 50% 大小的对象分配至 Humongous 区域,避免内存碎片 
 
- 可预测的停顿时间模型 - 通过 - -XX:MaxGCPauseMillis设定目标停顿时间(如 200ms),实现软实时回收
- 优先级回收策略:按 Region 垃圾价值(回收空间/耗时)排序,优先处理高收益 Region(Garbage-First 设计理念) 
- 衰减标准差算法:根据历史 GC 数据动态预测回收能力,确保目标时间内完成回收 
 
- 并行与并发混合执行 - 并行回收:利用多核 CPU 并行执行 Young GC 和 Mixed GC,缩短 STW 时间 
- 并发标记:后台线程与用户线程并发运行,仅初始标记/最终标记需短暂 STW 
- 增量式回收:将老年代回收拆分为多次 Mixed GC,避免单次长时间停顿 
 
- 高效空间整合与低碎片 - 全局使用标记-整理算法(Mark-Compact),局部使用标记-复制算法(Mark-Copy) 
- 零内存碎片:支持长期运行服务稳定分配大对象 
- 连续内存分配:通过指针碰撞(Bump-the-Pointer)直接分配,无需空闲链表管理 
 
- 智能化数据结构支持 - 记忆集(RSet):每个 Region 维护跨 Region 引用记录,避免全堆扫描 
- 全局卡片表(Card Table):标记 512B 卡片单元,精准追踪对象引用变化 
- 并发标记快照(SATB):记录标记开始时的对象引用关系,防止漏标 
 
- 适用场景对比 - 最大堆内存 - CMS:≤4GB 
- G1:≥8GB(支持 TB 级堆) 
 
- 停顿时间控制 - CMS:无明确预测模型 
- G1:可设定目标停顿时间 
 
- 内存碎片问题 - CMS:需定期 Full GC 整理 
- G1:自动整合(零碎片) 
 
- JDK 版本兼容性 - CMS:JDK8 及以下主流 
- G1:JDK7u4+ 可用,JDK9+ 默认 
 
 
- 调优建议 - 关键参数: - -XX:+UseG1GC 
- -XX:MaxGCPauseMillis=200 
- -XX:G1HeapRegionSize=4M 
- -XX:InitiatingHeapOccupancyPercent=45 
 
- 监控工具:使用 JMX 或 - jstat -gcutil观察 Region 分布与回收效率
 
CMS vs G1
垃圾回收器 CMS 和 G1 的比较?
- 分代与分区设计 - CMS:物理分代,严格划分新生代和老年代,需配合其他新生代收集器(如 ParNew)使用 
- G1:逻辑分代 + 物理分区,堆内存划分为多个等大小 Region(默认约 2048 个),支持动态调整分代 
 
- 回收算法与内存碎片 - CMS:基于标记-清除算法,老年代回收后产生内存碎片,可能触发 Full GC 
- G1:基于标记-整理算法,通过 Region 间对象移动避免内存碎片 
 
- 回收阶段差异 - CMS 四阶段: - 初始标记(STW 短暂) → 并发标记 → 重新标记(STW) → 并发清除 
 
- G1 四阶段: - 初始标记(STW) → 并发标记 → 最终标记(STW) → 筛选回收(按 Region 价值排序回收) 
 
 
- 停顿时间控制 - CMS:以最小停顿时间为目标,但无法预测具体停顿时间 
- G1:支持可预测停顿模型(如 - -XX:MaxGCPauseMillis=200ms),适合大堆场景
 
- 内存碎片与浮动垃圾 - CMS:存在内存碎片和浮动垃圾,可能因并发模式失败触发 Full GC 
- G1:无内存碎片,通过 SATB 机制避免浮动垃圾导致漏标 
 
- 大对象处理 - CMS:大对象直接进入老年代,加剧碎片问题 
- G1:大对象分配至 Humongous 区域(跨多个 Region),避免过早晋升 
 
- 辅助数据结构 - CMS:依赖 Card Table 记录跨代引用 
- G1:额外使用 Remembered Set(RSet)记录跨 Region 引用,占用约 20% Region 内存 
 
- 执行负载与读写屏障 - CMS: - 仅需写后屏障维护卡表(Card Table),记录跨代引用,同步操作开销低 
- 并发标记阶段使用增量更新策略,重新标记阶段计算量较小 
 
- G1: - 需写前屏障(支持 SATB 快照)和写后屏障(维护 RSet),异步队列处理开销高 
- 维护 Remembered Set(RSet)占用约 20% Region 内存,CPU 资源竞争更激烈 
 
 
- 适用场景 - CMS:中小型堆(≤4GB)、低延迟敏感场景(如 Web 服务),JDK8 及以下版本 
- G1:大型堆(≥8GB)、需平衡吞吐量与延迟的场景(如实时系统),JDK9+ 默认回收器 
 
什么情况下使用 CMS,什么情况使用 G1?
- CMS 适用场景 - 低延迟需求:适用于对响应时间敏感的应用(如 Web 服务、实时交易系统),需最小化 STW 停顿 
- 中小型堆内存(≤4GB):内存碎片风险可控,需预留 20% 空间存放浮动垃圾 
- 老年代为主的回收:适合对象晋升缓慢、老年代占用率高的场景(需配合 ParNew 收集器) 
- JDK 版本限制:推荐在 JDK 8 及以下版本使用 
 
- G1 适用场景 - 大堆内存(≥8GB):高效管理数十 GB 至数百 GB 堆内存,避免传统分代模型碎片问题 
- 可预测停顿时间:通过 - -XX:MaxGCPauseMillis设置目标停顿(如 200ms),适合金融交易等实时系统
- 内存碎片敏感场景:长期运行服务(如云原生应用),依赖标记-整理算法避免 Full GC 
- 混合代际回收:支持同时回收新生代和老年代,适合对象生命周期复杂的应用 
- JDK 版本兼容性:JDK 7u4+ 可用,JDK 9+ 默认推荐 
 
Java 垃圾回收
什么是 Java 里的垃圾回收?
为什么要 GC ?
如何触发垃圾回收?
定义
Java 垃圾回收(Garbage Collection, GC)是 JVM 提供的自动内存管理机制,用于回收程序中不再使用的对象所占用的内存空间
目标
其核心目标是如下 3 点:
- 内存泄漏:防止因对象长期未被释放导致的内存耗尽(如未关闭的文件句柄或数据库连接) 
- 手动管理复杂性:避免开发者手动分配/释放内存时可能出现的错误(如野指针、双重释放) 
- 内存碎片化:通过特定策略减少内存碎片,提升内存分配效率 
触发条件
- 内存分配失败 - Minor GC(新生代回收):当 Eden 区满时触发,通过复制算法快速回收存活率低的对象 
- Major GC(老年代回收):老年代空间不足时触发,通常伴随 Full GC 
 
- 显式调用 - 通过 - System.gc()或- Runtime.getRuntime().gc()建议 JVM 执行垃圾回收,但 JVM 可能忽略该请求
 
- 阈值触发 
 a. 堆内存使用率:当堆内存使用超过预设阈值(如 70%)时触发 GC
 b. 永久代/元空间满:Java 8 之前 PermGen 区满触发;Java 8+ Metaspace 区满触发
- 时间间隔 - 部分 JVM 实现基于固定时间间隔(如每小时)触发 GC,无论内存使用情况 
 
- 并发标记清除(CMS/G1) - 低延迟回收器(如 CMS、G1)可能在并发标记阶段因内存碎片等问题触发 Full GC 
 
stop the world
垃圾回收算法哪些阶段会 stop the world?
简述
STW 的触发与算法设计强相关
- 传统算法(如标记-清除、复制)的标记和转移阶段需全局暂停 
- 并发算法(如 CMS、G1)通过并发标记减少 STW,但关键阶段仍无法避免 
- 现代算法(如 ZGC)通过硬件优化(指针染色、读屏障)将 STW 压缩至极限 
实际应用中,需根据业务需求(低延迟或高吞吐)选择合适的回收器,并通过调整堆大小、分代策略等参数优化 STW 时间
详细介绍
- 标记阶段 - 初始标记(Initial Mark) - 触发原因:需暂停应用线程以确保根对象(GC Roots)的一致性,标记直接引用的存活对象 
- 适用算法:CMS、G1(混合回收的初始阶段) 
- 耗时:毫秒级(仅处理根对象) 
 
- 重新标记(Remark) - 触发原因:修正并发标记期间因应用线程修改引用导致的标记错误(漏标/误标) 
- 适用算法:CMS、G1(混合回收的最终标记阶段) 
- 耗时:毫秒级(通过写屏障优化缩短) 
 
 
- 清除与复制阶段 - 复制/转移阶段(Evacuation) - 触发原因:复制存活对象时需暂停线程,防止引用被修改 
- 适用算法:复制算法(年轻代回收)、G1(混合回收的复制阶段) 
- 耗时:与存活对象数量成正比(STW 主要瓶颈) 
 
- 清理阶段(Sweep) - 触发原因:统计分区存活情况时需短暂暂停线程 
- 适用算法:G1。 
- 耗时:微秒级(仅处理分区元数据) 
 
 
- 分代回收的 STW 场景 - 年轻代回收(Minor GC) - 触发原因:年轻代(如 Eden 区)空间不足时复制存活对象 
- 适用算法:所有分代算法(Serial、Parallel、G1 等) 
- 耗时:与年轻代存活对象数量相关(通常较短) 
 
- 老年代回收(Full GC) - 触发原因:老年代空间不足时触发全堆回收 
- 适用算法:Serial Old、Parallel Old、CMS(并发模式失败时退化) 
- 耗时:秒级(遍历全堆对象) 
 
 
- 现代算法的优化与例外 - 并发标记与增量回收 - CMS/G1:并发标记阶段无 STW,但初始/最终标记仍需暂停 
- ZGC/Shenandoah:通过读屏障和并发压缩,仅根扫描阶段需亚毫秒级 STW 
 
- Epsilon GC - 特点:无回收操作,完全避免 STW 
- 适用场景:特殊用途(如性能测试) 
 
 
minorGC & majorGC & fullGC
minorGC、majorGC、fullGC 的区别,什么场景触发 full GC
- 核心区别 
- Full GC 的触发场景 - 老年代空间不足 - 对象晋升或大对象直接分配至老年代失败(通过 - -XX:PretenureSizeThreshold控制大对象阈值)
 
- 显式调用 - System.gc()- 开发者主动触发(需未禁用 - -XX:+DisableExplicitGC)
 
- 元空间/永久代不足 - 类/方法元数据加载过多(需合理设置 - -XX:MaxMetaspaceSize)
 
- 晋升失败(Promotion Failure) - Survivor 区空间不足,且老年代无法提供担保空间 
 
- CMS 并发模式失败 - CMS 回收器无法及时回收足够空间,退化为 Serial Old 触发 Full GC 
 
- 分配大对象失败 - Eden 区无法分配大对象,且老年代空间不足 
 
- 空间分配担保失败 - 老年代连续空间 < 新生代存活对象总大小(未开启 - HandlePromotionFailure时触发)
 
- 堆转储或诊断操作 - 使用 - jmap -dump等工具强制触发
 
 
Minor GC 和 Major GC:详见 分代收集理论
回收方法区
主要回收两部分内容
- 废弃的常量 
- 不再使用的类型 
参考:回收方法区。
分代垃圾回收器
分代垃圾回收器是怎么工作的?
以 CMS 收集器为例说明
分代回收器有两个分区
- 新生代:默认的空间占比总空间的 1/3 
- 老生代:默认占比是 2/3 
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
- 把 Eden + From Survivor 存活的对象放入 To Survivor 区 
- 清空 Eden 和 From Survivor 分区 
- From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor 
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代
大对象也会直接进入老生代。老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程
GC流程
JVM 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代?
- GC 流程全解析 - 标记阶段 - 可达性分析:从 GC Roots(虚拟机栈局部变量、静态属性、常量等)出发标记存活对象 
- 两次标记机制:首次标记不可达对象,二次筛选未覆盖 - finalize()或已执行过的方法的对象直接回收
 
- 垃圾回收 - 新生代回收(Minor GC) - 触发条件:Eden 区空间不足时自动触发 
- 执行步骤: - 存活对象从 Eden 和 Survivor From 区复制到 Survivor To 区(年龄 +1) 
- 清空 Eden 和 Survivor From 区,交换 From/To 区角色 
- Survivor 空间不足时,存活对象通过分配担保机制直接进入老年代 
 
 
- 老年代回收(Full GC/Major GC) - 触发条件:老年代空间不足、大对象分配失败、显式调用 - System.gc()等
- 执行步骤: - 采用标记-整理算法清理老年代内存碎片 
- 回收后仍内存不足则抛出 - OutOfMemoryError
 
 
 
- 内存整理与分配 - 碎片整理:将存活对象紧凑排列(复制或移动)确保连续内存空间 
- 指针碰撞分配:直接分配连续内存,避免空闲链表管理开销 
 
 
- 对象晋升到老年代的条件 - 年龄阈值机制 - 对象每经历一次 Minor GC 且存活,年龄 +1,达到阈值(默认 15)时晋升 
- 调整参数: - -XX:MaxTenuringThreshold。
 
- 动态年龄判定 - Survivor 区中相同年龄对象总大小 > Survivor 空间的 50% 时,年龄 ≥ 该值的对象直接晋升 
 
- 大对象直存老年代 - 对象大小超过 - -XX:PretenureSizeThreshold设定值(如 30MB)时直接分配至老年代
 
- Survivor 区空间不足 - Minor GC 后存活对象无法全部放入 Survivor 区时提前晋升 
 
- 分配担保策略 - Minor GC 前检查老年代剩余空间: - 剩余空间 < 历次晋升对象的平均大小时触发 Full GC 
- Full GC 后仍不足则部分存活对象强制晋升 
 
 
 
面试题
垃圾回收器的基本原理是什么?
- 对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况 
- 通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象 
- 通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的" 
- 当 GC 确定一些对象为"不可达"时,GC 就有责任回收这些内存空间 
垃圾回收器可以马上回收内存吗?
- 可以 
有什么办法主动通知虚拟机进行垃圾回收?
- 程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行 
JVM 的元空间(永久代)中会发生垃圾回收么?
注意下 Java8 中已经移除了永久代,新加了一个叫做元空间的 native 内存区
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)
如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的
这就是为什么正确的永久代大小对避免 Full GC 是非常重要的原因
新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
- 新生代回收器:Serial、ParNew、Parallel Scavenge 
- 老年代回收器:Serial Old、Parallel Old、CMS 
- 整堆回收器:G1 
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低
老年代回收器一般采用的是标记-整理的算法进行垃圾回收
虚拟机执行子系统
类的生命周期
类的生命周期是 Java 虚拟机(JVM)管理类从加载到卸载的全过程,共分为五个阶段:
加载(Loading)→ 连接(Linking)→ 初始化(Initialization)→ 使用(Using)→ 卸载(Unloading)
详见 类加载的过程
类装载的执行过程
类装载是 Java 虚拟机(JVM)将类的字节码文件(.class)加载到内存并转化为可执行代码的过程
其执行流程可概括为加载、链接、初始化三个阶段,其中链接阶段进一步分为验证、准备、解析 三个子步
详见 类加载的过程
JVM 加载 class 文件的原理机制
- 类的加载流程 - 将其放在运行时数据区的方法区内 
- 然后在堆区创建一个 java.lang.Class 对象 
- 用来封装类在方法区内的数据结构 
 
- 类的加载的最终产品 - 是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构 
- 并且向 Java 程序员提供了访问方法区内的数据结构的接口 
- 通常是创建一个字节数组读入 .class 文件,然后产生与所加载类对应的 Class 对象 
 
- 加载完成后,Class 对象还不完整,所以此时的类还不可用 
- 当类被加载后就进入连接阶段,这一阶段包括: - 验证 
- 准备(为静态变量分配内存并设置默认的初始值) 
- 解析(将符号引用替换为直接引用)三个步骤 
 
- 最后 JVM 对类进行初始化,包括: - 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类 
- 如果类中存在初始化语句,就依次执行这些初始化语句 
 
类加载器
什么是类加载器,类加载器有哪些?
定义
通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到 Java 虚拟机外部去实现
以便让应用程序自己决定如何去获取所需的类
实现这个动作的代码被称为“类加载器”
分类
- 启动类加载器(Bootstrap Class Loader) - 由 JVM 自身用 C/C++ 实现,不继承 - java.lang.ClassLoader,无法在 Java 代码中直接访问
- 功能:加载核心 Java 类库(如 - rt.jar、- resources.jar),路径为- <JAVA_HOME>/jre/lib或- -Xbootclasspath指定的目录
 
- 扩展类加载器(Extension Class Loader) - 由 - sun.misc.Launcher$ExtClassLoader实现,父加载器为启动类加载器
- 功能:加载扩展目录( - <JAVA_HOME>/jre/lib/ext或- java.ext.dirs指定路径)中的 JAR 文件
 
- 应用程序类加载器(Application Class Loader/System Class Loader) - 由 - sun.misc.Launcher$AppClassLoader实现,父加载器为扩展类加载器
- 功能:默认加载用户类路径(ClassPath)下的类,是程序中默认的类加载器 
 
- 自定义类加载器(User-Defined Class Loader) - 开发者继承 - ClassLoader类实现,需重写- findClass()方法
- 用途:动态加载网络资源、加密字节码、模块化隔离(如 Tomcat 热部署)等 
 
详见 双亲委派模型
双亲委派模型
双亲委派模型是 Java 类加载器的层级委托机制
子加载器在加载类时会优先委派给父加载器处理,依次递归至最顶层的启动类加载器(Bootstrap ClassLoader)
若父加载器无法加载,子加载器才会自行加载,以此保证核心类库的安全性(如防止用户篡改 java.lang.String)并避免重复加载
详见 双亲委派模型
Tomcat 类加载器为什么要破坏双亲委派机制?open in new window
Java 程序运行机制
- 首先利用 IDE 集成开发工具编写 Java 源代码,源文件的后缀为 .java 
- 再利用编译器(javac 命令)将源代码编译成字节码文件,字节码文件的后缀名为 .class 
- 运行字节码的工作是由解释器(java 命令)来完成的 
 从上图可以看,java 文件通过编译器变成了 .class 文件,接下来类加载器又将这些 .class 文件加载到 JVM 中
	从上图可以看,java 文件通过编译器变成了 .class 文件,接下来类加载器又将这些 .class 文件加载到 JVM 中
	其实可以一句话来解释:类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构
Java 对象创建过程
首先让我们看看 Java 中提供的几种对象创建方式:
下面是对象创建的主要流程

- 虚拟机遇到一条 new 指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载 
- 类加载通过后,接下来分配内存 - 若 Java 堆中内存是绝对规整的,使用“指针碰撞“方式分配内存 
- 如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式 
- 划分内存时还需要考虑一个问题-并发,也有两种方式 - CAS 同步处理 
- 本地线程分配缓冲(Thread Local Allocation Buffer, TLAB) 
 
 
- 然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行 - init方法
Java 对象结构
Java 对象由三个部分组成:对象头、实例数据、对齐填充
- 对象头由两部分组成 - 第一部分存储对象自身的运行时数据:对象的哈希码、GC 分代年龄、锁标识状态、线程持有的锁、偏向线程 ID(一般占 32/64 bit) 
- 第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类) 
 
- 如果是数组对象,则对象头中还有一部分用来记录数组长度 
- 实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的) 
- 对齐填充:JVM 要求对象起始地址必须是 8 字节的整数倍(8 字节对齐) 
 
             
           
             
                         
             
            
评论区