第3章 Java/Kotlin基础与内存管理
内存管理是 Android 架构面试的必考核心,涵盖 JVM 内存模型、GC 原理、Kotlin 内存特性与泄漏场景。三大高频问题覆盖了从底层原理到实际优化,回答需兼顾理论深度与工程落地。
核心概念速查表
| 主题 | Java 侧重点 | Kotlin 侧重点 |
|---|---|---|
| 内存模型 | 堆、栈、方法区、PC、直接内存 | 空安全底层、内联类、对象头优化 |
| GC | 可达性分析、分代收集、CMS/G1 | 协程栈、挂起对象生命周期 |
| 内存泄漏 | Handler、单例、静态集合、匿名内部类 | 协程泄露、伴生对象、lazy 委托 |
Q1:请描述 JVM 运行时数据区域,Android 环境下哪些区域容易引发 OOM?
🎯 考察点:
- 是否理解堆、栈、方法区、本地方法栈、PC 寄存器
- 是否了解 Android 对方法区的限制(~64K)
- 能否结合 Bitmap、大图、Fragment 引用等场景分析 OOM
📐 回答框架:
- 罗列五大区域,强调堆和方法区是 OOM 高发区
- 说明 Android 上方法区(元空间)的限制与类加载泄漏
- 举例:Bitmap 堆外内存、Handler 匿名内部类持有 Activity
- 给出优化方向:复用、压缩、生命周期绑定
✏️ 简短示例回答:
JVM 分为堆、栈、方法区等。Android OOM 常见于堆内存(Bitmap 过大)和方法区(类加载过多导致 64K 限制)。应使用 LRU 缓存复用 Bitmap,避免匿名内部类隐式引用 Activity。
🔍 可能追问:
- 方法区何时会触发 OOM?如何监控?
- 栈溢出(StackOverflow)与 OOM 的区别是什么?
- Android 8.0 后方法区由 PermGen 变为 Metaspace,影响是什么?
⚠️ 避坑提醒:不要只背区域划分,必须结合 Android 实际场景(如 Fragment 不释放、图片加载库源码)。
Q2:GC 回收机制有哪些常用算法?如何判断一个对象已经“死亡”?
🎯 考察点:
- 是否清楚引用计数法和可达性分析的优劣
- 是否了解标记-清除、复制、标记-整理算法
- 是否知道分代收集及 Android 上 ART 的 GC 特点
📐 回答框架:
- 先答存活判定:GC Roots 向下搜索的引用链
- 再答三种算法:标记-清除(有碎片)、复制(浪费空间)、标记-整理(移动对象)
- 指出分代收集:Young GC 用复制,Old GC 用标记-整理
- 对比 ART 与 Dalvik 在并发 Mark Sweep 上的改进
✏️ 简短示例回答:
使用可达性分析从 GC Roots 出发标记存活对象。新生代常用复制算法,老生代用标记-整理。Android ART 默认使用并发标记清除(CMS),减少暂停。
🔍 可能追问:
- GC Roots 包括哪些?Activity 和 Service 的 Roots 区别?
- 为什么 ART 比 Dalvik GC 性能更好?具体用了什么技术?
- 如何手动触发 GC?System.gc() 有效吗?
⚠️ 避坑提醒:不要忽略 Android 特有的 GC 配置(如 largeHeap、onTrimMemory),这些是面试加分点。
Q3:Kotlin 与 Java 互操作时容易造成哪些内存泄漏?如何预防?
🎯 考察点:
- 是否理解 Kotlin 空安全、伴生对象、内联函数的内存影响
- 是否知道协程作用域失控导致的泄漏
- 是否了解匿名内部类(lambda)对 Activity 的隐式持有
📐 回答框架:
- 指出 Kotlin 伴生对象(companion object)本质是静态内部类,若持有外部引用则泄漏
- 分析 lambda 在 Activity 中隐式捕获 this 导致不能回收
- 强调协程需主动取消 Job 或绑定 lifecycleScope
- 对比 Java 方案:弱引用、非静态内部类改为静态+WeakReference
✏️ 简短示例回答:
Kotlin 伴生对象编译为静态内部类,若 lambda 中引用 Activity 属性则隐式持有。应使用 lifecycleScope 管理协程,并在 onStop 中取消,或改用 WeakReference 包裹 View 引用。
🔍 可能追问:
- Kotlin 的 object 声明与 Java 内部类有什么区别?
- 协程的 GlobalScope 为什么不好?如何正确替换?
- LeakCanary 检测 Kotlin 泄漏的原理是什么?
⚠️ 避坑提醒:不要只回答 kotlin 标准库的特性,要结合 Android 组件生命周期分析,展示工程敏感度。