Skip to content

Java 高频考点

语言基础


基本数据类型


数据类型字节大小范围备注
byte1 字节(8 位)-128 到 127
short2 字节(16 位)-32,768 到 32,767
int4 字节(32 位)-2,147,483,648 到 2,147,483,647
long8 字节(64 位)-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807结尾需加 L(如 100L
float4 字节(32 位)约 ±3.40282347E+38
(有效位数:6-7 位)
结尾需加 f(如 3.14f
double8 字节(64 位)约 ±1.79769313486231570E+308
(有效位数:15 位)
默认小数类型(如 3.14
char2 字节(16 位)0 到 65,535(对应 Unicode 字符,如 'A''中'用单引号表示(如 'a'
boolean不固定(通常 1 位/字节)truefalseJVM 可能优化为 1 位或占用 1 字节空间

自动装箱拆箱


自动拆箱:将包装类自动转换为基础类型

自动装箱:将基础类型自动转换为包装类型

java
Integer a = 10; // 自动装箱
int b = a;      // 自动拆箱

原理:

java
Integer a = Integer.valueOf(100);  // 自动装箱
int b = a.intValue();              // 自动拆箱

自动装箱拆箱场景:

  • 基本数据类型放入集合类,会将基础类型自动装箱为包装类型
  • 基本类型和包装类型比大小会自动拆箱为基本类型比较

注意,对于 Integer 是存在缓存的:

Integer a = 100;
Integer b = 100;
System.out.println(a == b);

结果为 true,因为 Java5 引入了缓存机制导致 -128 ~ 127 范围内直接采用缓存中的对象比较,因此在这个范围内的 Integer 对象的判断是相等的,在 Java6 中可以通过设置虚拟机参数:-XX AutoBoxCacheMax=size 修改


== 跟 equals 区别


== 运算符

  • 用于比较两个变量的引用,即判断它们是否指向同一个对象。
  • 对于基本数据类型(如 intchar 等),== 比较的是它们的值。

示例:

java
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // 输出 false,因为它们是不同的对象

equals() 方法

  • 用于比较两个对象的内容(即它们的状态)。
  • 默认情况下,Object 类中的 equals() 方法与 == 相同,但通常会被重写,以实现基于内容的比较。

示例:

java
System.out.println(str1.equals(str2)); // 输出 true,因为内容相同

综上:

  • 使用 == 比较引用(内存地址)。
  • 使用 equals() 比较内容(逻辑相等性)。

在比较字符串或自定义对象时,通常应该使用 equals() 方法。


接口和抽象类的区别


对比项接口(Interface)抽象类(Abstract Class)
定义定义方法的规范,不包含具体实现(仅方法声明)。不能被直接实例化的类,可包含抽象方法和具体方法。
示例PayService 接口,声明 pay 方法。AbstractPayService 抽象类,实现 PayService 接口并提供 pay 方法的具体逻辑。
方法实现- Java 8 前:所有方法默认抽象,无实现代码。
- Java 8 后:支持 default 默认方法。
可包含抽象方法(需子类实现)和具体方法(可直接继承使用)。
成员变量默认是 public static final,必须显式初始化。可以定义任意访问修饰符(private/protected/public)的成员变量,无需强制初始化。
构造器无构造器。可以有构造器(用于初始化成员变量),但类本身不能被实例化。
继承与实现类通过 implements 实现接口,支持多实现。类通过 extends 继承抽象类,仅支持单继承。
设计目的强调行为规范(“能做什么”),如定义跨类别的通用能力。提供部分共性实现(“是什么”),用于复用代码和约束子类行为。
适用场景需要统一方法规范但实现逻辑多样的场景(如支付、日志等)。需要封装部分公共逻辑,同时强制子类实现特定方法的场景(如模板模式)。

注意事项

  • 在实际开发中,通常会先定义接口,然后实现接口。
  • 如果多个实现类中有共同的可复用代码,可以在接口和实现类之间添加一个抽象类,将公共代码提取到抽象类中。

集合相关


ArrayList 和 LinkedList


特性ArrayListLinkedList
存储结构基于动态数组,直接存储数据内容。基于双向链表,存储节点(Node),每个节点包含数据及前后指针。
查询效率支持下标随机访问,时间复杂度为 O(1)不支持下标访问,需从头/尾遍历链表,平均时间复杂度为 O(n)
修改效率1.尾部操作:无扩容时 O(1)(扩容时需复制数组)。
2.非尾部操作:需移动元素,时间复杂度 O(n)
插入/删除只需修改相邻节点的指针,时间复杂度 O(1)(但定位操作仍需 O(n))。
线程安全非线程安全,需通过 Collections.synchronizedList 包装或使用局部变量。同 ArrayList,需外部同步或使用线程安全容器。
适用场景频繁查询、尾部增删。频繁在任意位置插入/删除。

ArrayList 扩容规则

  • 调用 ensureCapacity 方法,将数据长度乘以1.5倍。
  • 底层源码实现:当前数量 + 当前数量 >> 1位 相当于除以2。

ArrayList的Fail-Fast机制

  • 通过记录 modCount 参数来实现,增减元素会改变 modCount 的值。
  • 在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

HashMap


对比项JDK 1.7JDK 1.8
数据结构数组 + 链表(链表散列)数组 + 链表 + 红黑树
结构转换无红黑树结构链表长度 ≥ 8,且散列数组长度 ≥ 64 时转红黑树
数组长度允许非 2 的幂次方长度强制为 2 的幂次方长度(可以位运算优化哈希计算)
哈希计算通过取模计算槽位hash & (n-1) 替代取模
扩容时机插入数据前扩容插入数据后扩容(避免无效扩容,减少内存浪费)
扩容逻辑所有元素重新计算哈希并分配槽位通过高位判断元素是否迁移到新槽位(性能优化)
插入逻辑链表使用头插法(可能导致死循环)链表使用尾插法

为什么要在1.8之后改成尾插法

  • 1.7是头插法 1.8尾插法,链表头部插入元素会导致元素顺序和插入顺序颠倒,尾插法在尾部插入保持原来的顺序
  • 多线程扩容时容易产生并发死循环:
    • 线程 A 和线程 B 同时对同一个桶进行操作,线程 A 正在进行扩容操作,而线程 B 正在向该桶插入新元素
    • 由于头插法的特性,线程 B 插入的新元素会成为链表的新头节点
    • 由于扩容操作会将原链表的节点逆序插入到新链表中,线程 A 可能会在逆序过程中读取到线程 B 插入的新头节点

负载因子为什么选择0.75

  • 假设一个bucket空和非空的概率为0.5,通过二项式定理计算,当容量趋于无穷大时,合理值大概在0.7左右。
  • 由于临界值(threshold)= 负载因子(loadFactor)* 容量(capacity),而容量永远是2的幂。
  • 为了保证负载因子与容量的乘积是一个整数,0.75 是一个比较合理的选择,因为这个数与任何2的幂相乘的结果都是整数。

ConcurrentHashMap


对比项JDK 1.7JDK 1.8
数据结构分段数组(Segment)+ 链表(每个 Segment 独立锁)数组 + 链表 + 红黑树(类似 HashMap,锁粒度细化到链表头或树根节点)
锁机制分段锁(ReentrantLock),每个 Segment 独立加锁CAS + synchronized 锁单个桶(链表头或树根节点),锁粒度更小
并发限制Segment 数量决定(默认 16),并发性能受限于分段数无固定分段,锁粒度细化到桶级别,理论上并发度更高
哈希计算两次哈希(先定位 Segment,再定位桶)单次哈希(直接定位桶),优化哈希算法减少冲突
扩容机制每个 Segment 独立扩容,扩容期间阻塞该分段的写操作多线程协同扩容(迁移数据时,其他线程可协助完成桶迁移)
迭代器特性弱一致性(迭代过程中可能反映其他线程的修改)弱一致性,但实现更高效(基于快照或分段遍历)

为什么在1.8废弃了分段锁

  • 锁粒度优化:通过对单个节点加锁,降低了锁的竞争。
  • 锁性能优化:很多更新操作使用无锁的CAS操作,提高了效率,尤其在读多写少的场景下。
  • 内存优化:通过减少锁的数量和使用更简洁的数据结构,提高了内存效率。

多线程相关


创建线程的方式


四种创建方式

  • 继承 Thread 类,重写 run 方法
  • 实现 Runnable 接口,实现 run 方法
  • 实现 Callable 接口,创建 FutureTask 类对象通过构造方法传入 Callable 对象,最后再通过创建 Thread 对象传入 FutureTask
  • 通过线程池创建

注意

  • 一般用实现 runnable 接口,不用继承 thread 类,java 不支持多继承,但允许调用多个接口。
  • start 方法和 run 方法的区别:直接调用 run 是在当前线程运行的,start 则是启动新的线程运行方法。
  • Runnable 和 Callable 的区别:Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增加的。它们的主要区别是 Callable 的 call() 方法可以返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。

线程池核心参数


corePoolSize(核心线程数)

  • 初始线程数量,默认长期存活(即使空闲)
  • 特殊设置:allowCoreThreadTimeOut(true) 可使核心线程超时回收

maximumPoolSize(最大线程数)

  • 线程池扩容上限,触发条件:工作队列满 + 当前线程数 < max
  • 扩容创建的是"临时线程",受keepAliveTime控制

keepAliveTime(线程保活时间)

  • 控制临时线程的空闲存活时间
  • 计算公式:线程空闲时间 > keepAliveTime → 回收线程

workQueue(工作队列)

队列类型特性适用场景
ArrayBlockingQueue有界队列,数组实现流量突发控制
LinkedBlockingQueue可选有界/无界,链表实现默认无界(需警惕OOM)
SynchronousQueue零容量队列,直接传递任务高吞吐量场景
PriorityBlockingQueue优先级队列任务分级处理

threadFactory(线程工厂)

  • 用于设置创建线程
  • 通过 newThread() 方法提供创建线程,该方法创建的线程都是“非守护线程”而且“线程优先级都是默认优先级”

handler(拒绝策略):

策略类型特性适用场景
AbortPolicy直接抛出RejectedExecutionException异常,阻止系统继续运行需要快速失败并通知开发人员的场景
CallerRunsPolicy使用调用者线程直接执行被拒绝的任务适合需要保证任务一定被执行,且能接受调用线程阻塞的场景
DiscardOldestPolicy丢弃队列中最旧的任务(队首任务),然后尝试重新提交当前任务适合允许丢弃部分历史任务的场景
DiscardPolicy静默丢弃被拒绝的任务,不做任何处理适合允许任务丢失的非关键业务场景

可以通过实现 RejectedExecutionHandler 接口创建定制化策略。


线程间通信方式


同步机制特点主要用途
synchronized关键字修饰代码块/方法,自动获取/释放锁,不可中断,非公平锁,简单易用。单线程访问共享资源,避免竞态条件。
ReentrantLock显式锁(需手动释放),支持公平锁、可中断锁、超时锁,可绑定多个条件变量,灵活性高。需要更复杂锁控制的场景(如尝试获取锁、可中断操作)。
Semaphore基于许可证的同步,控制同时访问资源的线程数量,支持公平/非公平模式。限制并发线程数(如数据库连接池)。
CountDownLatch一次性同步工具,通过计数器等待其他线程完成,计数器归零后释放所有等待线程。主线程等待多个子线程完成初始化任务。
CyclicBarrier可重复使用的同步屏障,所有线程到达栅栏后继续执行,支持回调函数。多线程分阶段协同工作(如并行计算分阶段汇总结果)。
Phaser动态调整参与者数量,支持分层阶段同步,提供更灵活的到达/注销机制。复杂分阶段任务(如动态增减线程的迭代计算)。

synchronized 对比 ReentrantLock

  • synchronized是JVM实现的隐式锁,ReentrantLock是JDK代码实现的显式锁。
  • ReentrantLock可通过tryLock()避免死锁,synchronized不支持。

CountDownLatch 对比 CyclicBarrier

  • CountDownLatch计数器递减(一次性),CyclicBarrier计数器递增(可重复使用)。
  • CyclicBarrierreset()方法可重置,CountDownLatch不能重置。

Phaser

  • 支持动态注册(register())/注销(arriveAndDeregister())参与者。
  • 每个阶段(phase)可自定义同步策略,适合复杂任务编排。

CAS 和 AQS


以下是 CAS(Compare-And-Swap)AQS(AbstractQueuedSynchronizer) 的对比总结表格:

特性CAS(Compare-And-Swap)AQS(AbstractQueuedSynchronizer)
核心思想通过比较内存值与预期值是否一致来决定是否更新值。通过 CLH队列(双向链表)管理线程的排队与唤醒机制。
实现方式依赖底层硬件(CPU指令,如x86的cmpxchg)保证原子性。基于模板方法模式,子类需重写 tryAcquiretryRelease 等方法。
优点/缺点优点:无锁,避免线程阻塞
缺点:高竞争时自旋开销大
优点:支持独占和共享模式,适用高竞争场景
缺点:实现复杂,需要处理线程中断和超时逻辑
典型应用AtomicIntegerAtomicReferenceReentrantLockSemaphoreCountDownLatch
应用场景1. 实现无锁数据结构(如AtomicInteger)
2. 自旋锁
构建锁(如ReentrantLock)、同步工具(如Semaphore、CountDownLatch)

CAS的ABA问题

  • 问题:线程 A 修改变量和线程 B 修改变量的值相同,A 再次修改发现该变量未被修改过(实际上已经被修改过了)
  • 解决方式:使用版本号(如AtomicStampedReference)或时间戳标记变量状态。

AQS的核心机制

  • 通过state变量表示同步状态(如锁的持有次数)。
  • 线程竞争失败后,会进入CLH队列并自旋检查前驱节点状态,避免频繁上下文切换。

JVM 相关


JVM 的组成


  • ClassLoader(类加载器)
  • Runtime Data Area(运行时数据区,内存分区)
    • Stack(Java 虚拟机栈)
    • Heap(Java 堆)
    • Method Area(方法区)
    • PC Register(程序计数器)
    • Native Method Stack(本地方法栈)
  • Execution Engine(执行引擎)
  • Native InterFace(本地库接口)

运行流程

  • 类加载器:把Java代码转换为字节码
  • 运行时数据区:把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是由执行引擎运行
  • 执行引擎:将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口来实现整个程序的功能。

JVM 运行时数据区


区域线程私有/共享存储内容异常类型关键参数
程序计数器私有当前指令地址
Java 虚拟机栈私有栈帧(局部变量、操作数栈等)StackOverflowError/OOM-Xss
本地方法栈私有Native 方法调用信息StackOverflowError/OOM无(与虚拟机栈合并)
共享对象实例、数组OOM-Xms, -Xmx
方法区(元空间)共享类信息、常量、静态变量、JIT代码OOM(JDK8 前 PermGen 相关)-XX:MetaspaceSize
  • Java堆:唯一存放java对象实例的空间,受垃圾回收器管理的区域
  • 方法区:可以认为是java堆的一部分,两者可以被所有线程共享的,用于存储已被虚拟机加载的的信息,常量静态变量和即时编译器JIT编译后的代码。
  • 虚拟机栈和本地方法栈:
    • 栈一般指的是虚拟机栈,他是线程私有的,和线程的生命周期相同,方法执行时会在其中创建栈帧,包含存储局部变量表、操作数栈等信息。栈帧的入栈和出栈对应方法的调用和执行过程。
    • 本地方法栈主要是为了本地方法服务的,在HotSpot虚拟机中将两者合二为一了。一般指的栈内存指的是java虚拟机栈的局部变量表部分。
  • 程序计数器PC:存放了当前线程所执行的字节码的行号,因此每条线程都有独立的PC,在java虚拟机规范中,PC这部分所在的区域是唯一一个没有规定任何OOM异常的区域,因此该区域也不会进行GC(垃圾回收)

垃圾回收算法


算法标记-清除 (Mark-Sweep)标记-复制 (Mark-Copy)标记-整理 (Mark-Compact)
算法原理1. 标记所有存活对象
2. 清除未标记的垃圾对象
1. 将内存分为两块,每次只用一块
2. 标记存活对象并复制到另一块
3. 清空当前块
1. 标记所有存活对象
2. 将存活对象向内存一端移动
3. 清理边界外的空间
优点/缺点优点:执行速度快,内存利用率高
缺点:内存碎片化严重,分配大对象时可能失败
优点:无内存碎片, 分配效率高
缺点:内存浪费(需预留一半空间),复制存活对象开销大
优点:无内存碎片,内存利用率高(无需分区)
缺点:整理过程耗时,暂停时间较长
适用场景老年代(存活对象多,碎片容忍)新生代(存活对象少,复制成本低)老年代(避免碎片,需连续内存)
JVM应用CMS 收集器的并发标记阶段Serial/ParNew 收集器的新生代Serial Old/CMS 的 Full GC 阶段
代表GC器CMS(并发标记清除)Parallel Scavenge(复制算法)G1/ZGC(局部整理)

分代回收

  • 新生代:多用标记-复制(如 Eden + Survivor 区)。
  • 老年代:多用标记-清除或标记-整理(如 CMS 和 G1)。
    G1 收集器将堆划分为多个 Region,局部使用复制或整理算法。

现代 GC

  • G1 垃圾回收器:通过预测停顿时间,动态选择 Region 进行局部整理和复制。
  • ZGC/Shenandoah 垃圾回收器:通过染色指针、读屏障等技术减少 STW 时间,实现并发整理。

页面历史

Released under the CC BY-NC-SA 4.0 License

Copyright © 2025 OFFER DASH