第3章 Java/Kotlin基础与内存管理

内存管理是 Android 架构面试的必考核心,涵盖 JVM 内存模型、GC 原理、Kotlin 内存特性与泄漏场景。三大高频问题覆盖了从底层原理到实际优化,回答需兼顾理论深度与工程落地。

高频基础题 · 回答框架 JVM 内存模型 GC 回收机制 Kotlin 内存特性 ① 堆/栈/方法区/PC ② 线程私有 vs 共享 ③ 对象分配流程(TLAB) ① 可达性分析存活判定 ② 标记-清除/复制/整理 ③ 分代收集 Young/Old ① 空安全 (? / !! / let) ② 协程作用域泄漏防范 ③ 伴生对象/匿名内部类 加分点:结合 OOM 案例 加分点:分代年龄细节 加分点:协程取消与泄漏

核心概念速查表

主题 Java 侧重点 Kotlin 侧重点
内存模型 堆、栈、方法区、PC、直接内存 空安全底层、内联类、对象头优化
GC 可达性分析、分代收集、CMS/G1 协程栈、挂起对象生命周期
内存泄漏 Handler、单例、静态集合、匿名内部类 协程泄露、伴生对象、lazy 委托

Q1:请描述 JVM 运行时数据区域,Android 环境下哪些区域容易引发 OOM?

🎯 考察点:

  • 是否理解堆、栈、方法区、本地方法栈、PC 寄存器
  • 是否了解 Android 对方法区的限制(~64K)
  • 能否结合 Bitmap、大图、Fragment 引用等场景分析 OOM

📐 回答框架:

  1. 罗列五大区域,强调堆和方法区是 OOM 高发区
  2. 说明 Android 上方法区(元空间)的限制与类加载泄漏
  3. 举例:Bitmap 堆外内存、Handler 匿名内部类持有 Activity
  4. 给出优化方向:复用、压缩、生命周期绑定

✏️ 简短示例回答:

JVM 分为堆、栈、方法区等。Android OOM 常见于堆内存(Bitmap 过大)和方法区(类加载过多导致 64K 限制)。应使用 LRU 缓存复用 Bitmap,避免匿名内部类隐式引用 Activity。

🔍 可能追问:

  • 方法区何时会触发 OOM?如何监控?
  • 栈溢出(StackOverflow)与 OOM 的区别是什么?
  • Android 8.0 后方法区由 PermGen 变为 Metaspace,影响是什么?
⚠️ 避坑提醒:不要只背区域划分,必须结合 Android 实际场景(如 Fragment 不释放、图片加载库源码)。

Q2:GC 回收机制有哪些常用算法?如何判断一个对象已经“死亡”?

🎯 考察点:

  • 是否清楚引用计数法和可达性分析的优劣
  • 是否了解标记-清除、复制、标记-整理算法
  • 是否知道分代收集及 Android 上 ART 的 GC 特点

📐 回答框架:

  1. 先答存活判定:GC Roots 向下搜索的引用链
  2. 再答三种算法:标记-清除(有碎片)、复制(浪费空间)、标记-整理(移动对象)
  3. 指出分代收集:Young GC 用复制,Old GC 用标记-整理
  4. 对比 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 的隐式持有

📐 回答框架:

  1. 指出 Kotlin 伴生对象(companion object)本质是静态内部类,若持有外部引用则泄漏
  2. 分析 lambda 在 Activity 中隐式捕获 this 导致不能回收
  3. 强调协程需主动取消 Job 或绑定 lifecycleScope
  4. 对比 Java 方案:弱引用、非静态内部类改为静态+WeakReference

✏️ 简短示例回答:

Kotlin 伴生对象编译为静态内部类,若 lambda 中引用 Activity 属性则隐式持有。应使用 lifecycleScope 管理协程,并在 onStop 中取消,或改用 WeakReference 包裹 View 引用。

🔍 可能追问:

  • Kotlin 的 object 声明与 Java 内部类有什么区别?
  • 协程的 GlobalScope 为什么不好?如何正确替换?
  • LeakCanary 检测 Kotlin 泄漏的原理是什么?
⚠️ 避坑提醒:不要只回答 kotlin 标准库的特性,要结合 Android 组件生命周期分析,展示工程敏感度。