【Android面试】Java专题

文章目录


历史资料:

一、Java 基础

1. == 与 equals () 区别?String 类重写 equals 做了哪些逻辑?

  • == 为比较运算符,基本类型比较数值,引用类型比较内存地址
  • equals 是 Object 原生方法,默认逻辑等同于 ==,需要子类重写才能实现内容比较。
  • String 重写 equals:先判断引用地址是否相同,再判断类型是否一致,最后逐字符对比内容,实现字符串内容相等判定。(比较两个对象的内容,需要重写equals)

2. 为什么重写equals方法需要重写hashcode

因为两个相等的对象的 hashCode 值必须是相等。
也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
关于hashcode:

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

3. final、finally、finalize 三者区别;final 修饰基本类型、引用类型、方法、类的限制

  • final 是修饰符:修饰基本类型数值不可修改;修饰引用类型引用地址不可变,但对象内部属性可修改;修饰方法禁止重写;修饰类禁止继承。
  • finally 是异常处理代码块,配合 try-catch 使用,用于资源释放,除系统退出外都会执行。
  • finalize 是 Object 废弃方法,JDK9 已移除,GC 回收时机不可控,Android 开发中完全不推荐使用。

4. 接口和抽象类区别,Android 开发中各自适用场景

  • 抽象类:拥有构造方法、成员变量,支持普通方法与抽象方法,仅支持单继承,可维护成员状态
  • 接口:无构造方法,仅定义行为,支持多实现、多继承,默认变量为常量,适合解耦与规范定义。
  • Android 场景:抽象类用于 BaseActivity、BaseViewModel抽取通用基础逻辑;接口用于点击回调、网络监听、跨模块通信、行为约束

5. 深拷贝与浅拷贝区别,Android 中如何实现对象深拷贝

  • 1、浅拷贝:只复制顶层对象引用,内层子对象共享内存,修改子对象数据会相互影响。
  • 2、深拷贝:递归复制所有层级对象,新旧对象内存完全隔离,数据互不干扰。
  • 3、Android 实现方式:手动逐层赋值、重写 clone 方法、Serializable 序列化、Parcelable
    序列化、Gson/JSON 序列化。

6. String、StringBuilder、StringBuffer 区别,线程安全与性能差异

  • 1、String 不可变,底层为 final 字符数组,字符串拼接会频繁创建新对象,性能差。
  • 2、StringBuffer 方法加 synchronized 锁,线程安全,多线程场景使用,性能偏低。
  • 3、StringBuilder 无锁、线程不安全,单线程高性能,是 Android 日常字符串拼接首选。

7. 泛型原理、协变与逆变? extends / ? super 适用场景

  • 1、泛型核心为类型擦除,编译期做类型校验,运行期擦除为 Object,不存在实际泛型类型
  • 2、? extends T 协变:只读不写,代表 T 及子类集合,适合数据读取场景
  • 3、? super T 逆变:只写少读,代表 T 及父类集合,适合数据存入场景
    对比kotlin泛型

8. 自动装箱拆箱原理,Integer 缓存机制及坑点

  • 1、装箱:基本类型自动转为包装类,调用 Integer.valueOf () 等方法;拆箱:包装类自动转为基本类型,调用 intValue ()。
  • 2、Integer 存在缓存,默认缓存 -128~127 区间数字,该区间对象复用,地址相同。
  • 3、超出缓存范围会新建对象,使用 == 判断包装类会出现逻辑错误,业务中推荐使用 equals。

9. 反射原理,Android 中反射的使用场景、性能问题与优化方案

  • 反射在运行期获取类、方法、字段信息,突破访问权限限制,动态调用代码。
  • Android 场景:框架源码、兼容适配、动态插件化、注解框架、系统 API 隐藏方法调用。

10. 注解分类(元注解 / 运行时 / 编译时),Android 常用注解与 APT 原理

  • 1、元注解:用于修饰其他注解,包含 Retention、Target、Documented、Inherited 等。
  • 2、编译时注解:编译期生效,通过 APT 扫描解析,生成辅助代码,无运行时损耗。如@Override
  • 3、运行时注解:运行期通过反射解析,Android 路由、依赖注入框架大量使用 APT 编译时注解提升性能。@BindView

11. Error 和 Exception 区别,受检异常 & 非受检异常,安卓开发异常处理规范

  • 1、Error 为系统级严重错误,如 OOM、栈溢出,无法手动捕获处理
  • 2、Exception 为可处理异常,分为受检异常(编译期强制捕获)和运行时异常(RuntimeException)
  • 3、Android 开发尽量捕获运行时异常,避免崩溃,禁止空指针裸奔,核心链路做好异常兜底

12. Serializable 与 Parcelable 区别、性能对比,Android 跨进程传输选型

  • 1、Serializable 基于 JVM 反射实现,使用简单,序列化效率低,内存开销大。
  • 2、Parcelable 安卓专属,手动编写序列化逻辑,内存拷贝高效,跨进程通信首选。
  • 3、选型原则:本地简单缓存用 Serializable;Activity 传值、Binder 跨进程通信必须用 Parcelable。

13. 静态变量、成员变量、局部变量生命周期与初始化顺序

  • 1、静态变量:类加载时初始化,生命周期跟随整个应用,全局唯一。
  • 2、成员变量:对象创建时初始化,生命周期与对象一致,存在堆内存。
  • 3、局部变量:方法调用时创建,方法结束立即销毁,无默认初始值,需手动赋值。

14. Java 四大引用(强 / 软 / 弱 / 虚),各自特点、GC 回收策略、Android 实际应用

  • 1、强引用:默认引用,GC 永远不会回收,Android 静态持有 Activity 会引发内存泄漏。
  • 2、软引用:内存不足时才会被回收,适合实现图片缓存、临时缓存数据。
  • 3、弱引用:下次 GC 直接回收,常用于 Handler 持有 Activity、防止内存泄漏。
    弱引用是一种生命周期不强于 GC 的引用类型,适用于需要自动清理、避免内存泄漏的场景,如缓存、监听器、ThreadLocal 等。
  • 4、虚引用:无法单独使用,仅用于监控对象回收,一般用于内存监控与资源释放。

二. Java集合

1. ArrayList 底层原理、扩容机制、线程不安全原因

  • 底层基于动态 Object 数组实现,初始容量为 10,支持快速随机访问。
  • 扩容机制:元素满了之后,自动扩容为原容量的 1.5 倍,新建数组并拷贝原数据。
  • 线程不安全:add、remove 等方法无锁,多线程并发写入会出现数据覆盖、数组越界问题。

2. LinkedList 底层结构,与 ArrayList 增删查性能对比,适用场景

  • LinkedList 底层为双向链表,无数组扩容开销,节点前后相互引用。
  • 查询:ArrayList 随机访问更快;增删:LinkedList 中间 / 头部增删效率更高。
  • 适用场景:频繁增删操作使用 LinkedList;大量查询、遍历场景优先使用 ArrayList。

3. HashMap 底层原理(JDK1.8)、树化时机、扩容机制

  • 底层采用 数组 + 单向链表 + 红黑树 结构,通过 hash 算法定位数组下标。
  • 树化条件:链表长度大于 8 且数组容量大于 64 转为红黑树;退化条件:节点小于 6 退回链表
  • 扩容为原容量 2 倍,重新计算 hash 分布,拆分高低位链表,减少重新 hash 开销
    在这里插入图片描述
    这里的0,1,2是一个桶位,key经过hash算法计算得到桶位(多个key可能对应一个桶位)
    链表长度:指单个桶内的数组长度
    数组容量:指整个长度,1/2/3…

4. HashMap 为什么线程不安全?多线程下会出现哪些问题

  • 无加锁保护,并发 put 会导致数据覆盖,元素丢失。
  • JDK1.7 头插法在扩容时会引发链表环,造成死循环 CPU 100% (JDK1.8以后采用尾插法)
  • 并发扩容、并发遍历会触发元素丢失、空指针、数据错乱等问题

5. HashTable、ConcurrentHashMap 区别,JDK1.7 和 1.8 ConcurrentHashMap 加锁差异

  • HashTable 全局锁(synchronized),并发效率极低,不推荐在开发中使用
  • JDK1.7 ConcurrentHashMap:分段锁 Segment,分段加锁提升并发度
  • JDK1.8 ConcurrentHashMap:取消分段锁,采用 CAS + synchronized锁住链表头节点,粒度更细、并发更高

6. HashSet、LinkedHashSet、TreeSet 底层实现与特性区别

  • HashSet 底层依托 HashMap,无序不可重复,查询效率最高。
  • LinkedHashSet 基于链表有序,保留元素插入顺序。
  • TreeSet 基于红黑树,元素自然排序或自定义比较器排序

7. 遍历集合时 ConcurrentModificationException 产生原因及解决方案

  • 原因:迭代器遍历过程中,直接调用集合 add/remove,修改了 modCount 版本号
  • 解决方案:使用迭代器自带的 remove 方法,不会修改版本号
    高并发场景:使用 CopyOnWriteArrayList、ConcurrentHashMap 并发安全集合

三、JUC 并发编程

1. 线程与进程区别,Android 主线程、子线程职责与限制

  • 1、进程是资源分配最小单位,线程是 CPU 调度最小单位,一个进程包含多个线程
  • 2、Android 主线程:负责 UI 绘制、事件分发、刷新界面,禁止耗时 IO、网络、计算
  • 3、子线程:处理网络请求、文件 IO、大数据解析、耗时计算,禁止直接操作 UI

2. 线程生命周期 6 种状态及流转过程

  • 1、NEW:线程对象创建完成,未调用 start 方法
  • 2、RUNNABLE:包含就绪 + 运行,等待 CPU 调度或正在执行
  • 3、BLOCKED:等待获取 synchronized 排他锁,主动阻塞
  • 4、WAITING:无限等待,需手动唤醒,如 wait、join
  • 5、TIMED_WAITING:限时等待,超时自动唤醒,如 sleep、延时 wait
  • 6、TERMINATED:线程代码执行完毕或异常终止,生命周期结束
    在这里插入图片描述

3. synchronized 底层原理、锁升级机制(偏向锁 / 轻量级锁 / 重量级锁)、锁范围

  • 底层原理:通过 JVM 的 monitor 机制实现的,每个对象都有一个 monitor,线程通过获取和释放 monitor 来实现同步
  • 锁升级顺序:偏向锁 → 轻量级锁 → 重量级锁,只升级不降级,优化无竞争场景性能。
  • 修饰实例方法锁当前对象;静态方法锁类对象;代码块锁自定义加锁对象

4. volatile 关键字作用、底层内存屏障原理,能否保证原子性?适用场景

  • 1、保证可见性:强制变量刷新主内存,多线程之间实时可见,消除工作内存缓存
  • 2、禁止指令重排序:通过内存屏障屏蔽 JVM 和 CPU 乱序优化,避免逻辑错乱
  • 3、不保证原子性,复合操作如 i++ 依旧线程不安全,适合状态标记、开关变量。

5.Lock 接口与 synchronized 区别

  • 1、synchronized 是 JVM 原生隐式锁,自动释放;Lock 是 Java 代码层显式锁,手动加锁解锁
  • 2、ReentrantLock 支持可重入(二者都是)、可中断等待、超时锁、公平 / 非公平锁,功能更强大
  • 3、低并发简单场景用 synchronized;高并发、细粒度控制、复杂同步场景用 ReentrantLock。

6. CAS 原理、自旋锁、ABA 问题及解决方案

  • 1、CAS 是乐观无锁机制,包含期望值A、内存值V、新值B,匹配成功才更新。
    如果内存中的值 V 等于预期值 A,就将内存中的值更新为 B;否则不更新。
  • 2、自旋锁基于 CAS 循环尝试获取锁,不阻塞线程,适合并发竞争较小场景。(自旋锁不会让线程进入阻塞状态,而是让线程在获取锁失败时持续“自旋”(循环检查锁是否可用),直到成功获取锁为止。
  • 3、ABA 问题:值重复修改导致校验失效,解决方案:添加版本号、使用 AtomicStampedReference。

7. 线程池核心参数

  • 1、核心线程数:长期存活常驻线程,即使空闲也不会回收。
  • 2、最大线程数:线程池允许创建的最大线程数量,限制资源上限。
  • 3、阻塞队列:存放等待任务;
  • 4、拒绝策略:任务饱和时的兜底处理,防止无限堆积
    在这里插入图片描述

8. Android 为什么不建议直接使用 Executors 创建线程池?手写自定义线程池

  • 1、Executors 封装的线程池多存在无界队列、无限最大线程,容易引发 OOM、资源耗尽。
  • 2、安卓对系统资源限制严格,Executors 无法适配移动端内存管控。
  • 3、生产必须自定义 ThreadPoolExecutor,合理配置核心线程、队列容量、拒绝策略。

9. threadLocal 原理、底层数据结构、内存泄漏原因,Android 应用场景

  • 1、底层:每个线程持有独立 ThreadLocalMap,key 为弱引用 ThreadLocal,数据线程隔离。
  • 内存泄漏原理分析:
    每个 Thread 对象内部维护一个 ThreadLocalMap,这个 Map 的 key 是 ThreadLocal 实例(使用弱引用 WeakReference),value 是线程本地变量。
    当 ThreadLocal 实例不再被外部引用时,key 会被垃圾回收。
    但 value 仍然存在于 ThreadLocalMap 中,只要线程没有终止,value 就不会被回收,从而造成内存泄漏。
  • 3、安卓场景:用户信息隔离、上下文存储、框架数据隔离,使用后必须主动 remove。

10. wait/notify/notifyAll 原理与使用注意事项,sleep 与 wait 区别

  • 1、wait/notify 必须在同步代码块中调用,依赖对象锁,调用后主动释放锁
  • 2、sleep 是 Thread 静态方法,不释放锁;wait 是 Object 方法,主动放弃锁资源
  • 3、notify 随机唤醒单个等待线程,notifyAll 唤醒全部,避免线程永久阻塞
  • 4、sleep用于暂停执行,wait用于线程交互

11. 死循环、死锁产生的必要条件,如何避免和排查死锁

  • 死锁四大必要条件:互斥、请求保持、不可剥夺、循环等待
  • 避免方案:统一锁获取顺序、设置锁超时、减少多锁嵌套、使用可中断锁
  • 排查方式:线程快照、日志分析、ANR 日志抓取,定位互相持有锁的线程。

12. CountDownLatch、CyclicBarrier、Semaphore 作用与场景区别

  • 1、CountDownLatch:一次性计数器,主线程等待多个子线程全部完成再执行。
  • 2、CyclicBarrier:可循环复用,多个线程互相等待,全部到达临界点后统一执行。
  • 3、Semaphore:信号量控制并发数量,用于限流、资源抢占、连接数控制

13. 并发三大问题:原子性、可见性、有序性,分别如何保证

  • 原子性:一个操作要么全部执行完成,要么完全不执行,中间状态对外不可见,即该操作是不可被中断的。

使用 synchronized、Lock、CAS、原子类保证复合操作不可分割

  • 可见性:当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。

使用 volatile、synchronized 刷新主内存,保证多线程实时可见

  • 有序性:程序执行的顺序按照代码的先后顺序执行。但由于编译器优化、CPU 指令重排序,实际执行顺序可能与代码顺序不同。

volatile 内存屏障、synchronized 独占锁,禁止指令重排序。

14. 安卓主线程更新 UI 原理,子线程切换主线程的多种实现方式与底层差异

  • Android UI 控件非线程安全,控件内部加锁校验,禁止子线程直接更新。
  • 切换方式:Handler、View.post、runOnUiThread、Coroutine 调度器。
  • 底层统一依赖 Handler 消息机制,通过 Message 抛到主线程 Looper 循环执行。

四. JVM 虚拟机

1. JVM 内存区域划分(堆、栈、方法区、本地方法栈、程序计数器),各区域 OOM 场景

在这里插入图片描述

  • 程序计数器:线程私有,唯一无 OOM 区域,记录字节码执行位置
  • 虚拟机栈 / 本地方法栈:方法嵌套过深、递归无限调用,触发栈溢出
  • Java 堆:存放所有对象实例,大图片、内存泄漏、海量对象导致堆 OOM
  • 方法区 / 元空间:存储类信息、常量池,动态加载大量类、插件化易引发溢出

2. Java 垃圾回收机制,可达性分析算法 vs 引用计数算法

  • JVM 主流采用 GC Roots 可达性分析,从根对象遍历,不可达对象判定为垃圾
  • 引用计数法:对象引用 + 1、释放 - 1,无法解决循环引用,已被废弃
  • 安卓组件之间互相持有引用普遍,可达性分析更适合移动端内存回收

3. 垃圾收集器分类,CMS、G1 核心回收流程与优缺点

  • CMS 并发低延迟,分为初始标记、并发标记、重新标记、并发清除,碎片严重。
  • G1 分区收集,兼顾吞吐量与低停顿,可预测回收时间,适合大内存设备
  • Android 的垃圾回收(GC)由 ART 虚拟机负责,核心是分代回收 + 可达性分析 + 低延迟并发,自动回收不再引用的对象,避免内存泄漏与 OOM

4. 年轻代、老年代划分,对象分配规则、大对象直接进入老年代场景

  • 堆划分为年轻代 (Eden + 两个 Survivor) 和老年代,长期存活对象晋升老年代。
  • 新对象优先分配在 Eden 区,Eden 满触发 Minor GC 复制存活对象。

5. 常见 GC 类型:Minor GC、Major GC、Full GC 触发条件

  • Minor GC:Eden 区空间不足,频率高、速度快,卡顿影响小
  • Major GC:老年代空间不足,通常伴随 Minor GC,耗时更长
  • Full GC:全堆 + 方法区整体回收,开销极大,会造成安卓卡顿、ANR,需严格规避

6. 类加载全过程(双亲委派模型),双亲委派作用,Android 中 ClassLoader 破坏场景

  • 类加载流程:加载→验证→准备→解析→初始化,完整加载 Class 字节码
  • 双亲委派:子加载器优先委托父加载器,保证核心类安全、全局唯一
  • 安卓热修复、插件化、动态化框架,都会主动破坏双亲委派实现类替换。

7. JVM 调优核心参数,线上 / ANR 场景如何排查内存抖动、内存泄漏、OOM

  • 核心参数:堆初始大小、最大堆、新生代比例、GC 回收器类型。
  • 内存泄漏:静态持有、资源未关闭、匿名内部类、集合常驻元素。
  • 排查手段:LeakCanary、Profiler 内存快照、ANR 日志、MAT 堆文件分析。

8. 方法区与元空间区别,JDK1.8 元空间优化点

  • JDK1.8 之前方法区位于堆内存,容易溢出;1.8 彻底废弃永久代。
  • 元空间使用本地直接内存,不受堆内存限制,动态扩容。
  • 优化了类、方法、常量池存储,降低方法区溢出概率。

9. 对象在 JVM 中的内存布局、对象头作用

  • 对象内存布局:对象头、实例数据、对齐填充三部分组成
  • 对象头包含 MarkWord 和类型指针,存储锁状态、GC 标记、哈希码、分代年龄
  • 锁升级、偏向锁、GC 回收标记,全部依赖对象头字段实现

10. 栈溢出和堆溢出的区别,Android 开发中常见溢出场景

  • 栈溢出:栈空间固定,递归死循环、方法嵌套过深,抛出 StackOverflowError
  • 堆溢出:对象过多、大图未回收、内存泄漏,导致堆内存耗尽触发 OOM
  • 安卓常见:大图内存泄漏、静态持有 Activity、无限递归、循环创建对象

11. 即时编译、解释编译区别,JVM 编译优化手段

  • 解释编译:边解释边执行,启动快、执行效率低;即时编译 (JIT) 热点代码编译机器码
  • JVM 混合模式:热点方法即时编译,平衡启动速度与运行性能
  • 优化手段:逃逸分析、锁消除、锁粗化、标量替换,减少内存开销提升执行效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值