最近后台一直有人问我要Java面试题,尤其是基础部分。说实话,网上Java面试题一抓一大把,但大部分要么是零散的知识点罗列,要么是直接照着别人的博客抄,连答案错没错都不管。我整理这份“基础题122道”其实是把过去几年面试别人、被别人面试、以及帮朋友辅导时反复出现的高频考点做了一次系统归拢。本文不打算把122道题一股脑贴出来,那没有意义,我想把这份题库背后的设题逻辑、核心考点拆解、备考方法和踩坑经验讲清楚,你拿到这份清单之后能真正用起来。
1. 这122道基础题是怎么整理出来的——先聊聊设题逻辑
1.1 基础题在Java面试中的真实分量
很多人有一个误区,觉得Java面试就是拼框架、拼项目、拼算法,基础题只是走个过场。实际我面过的人里,挂掉最多的恰恰是基础题环节。尤其是三年以内的开发岗,面试官不可能拿高并发架构和分布式事务来压你,反而会从JVM内存、集合源码、线程状态这些“最没技术含量”的问题切入,探你的底子。基础题答得是否流畅、是否有深度,直接决定了面试官对你技术水平的初始判断。
我见过一个候选人,简历上写着熟悉Spring Cloud微服务治理,结果我问“HashMap在JDK 8里什么时候转红黑树”,他支支吾吾说“好像是链表太长”。这就很尴尬了。项目经验可以包装,简历技能可以注水,但基础题一问一个准,根本装不了。所以我认为,基础题不是面试的“开胃菜”,而是“照妖镜”。
1.2 考官出基础题的三个层次:记忆、理解、应用
我在整理这122道题时,刻意按照考官的出题逻辑做了分层。第一层是记忆类题目,比如“Java的基本数据类型有哪些”“String和StringBuilder的区别”,这类题考察你有没有系统学过Java,属于送分题,但送分题也经常有人答不完整,比如基本数据类型漏掉char或者boolean的占用位数记错。
第二层是理解类题目,比如“HashMap的put方法流程是什么”“synchronized和ReentrantLock的区别有哪些”,这类题要求你能讲清楚底层原理和设计思想。第三层是应用类题目,比如“什么场景下会内存溢出,怎么排查”“线程池的拒绝策略怎么选”,这类题考察你在实际项目中能不能把基础知识用起来。三个层次逐级递进,只靠背答案是过不了理解层和应用层的。
1.3 122道题的分类框架与考点分布
我在整理时把122道题分成了七个大块:Java基础语法、面向对象、集合框架、并发编程、JVM与内存、IO与异常、新特性与工具。这个分类基本对应了绝大多数Java岗位JD里“熟悉Java核心基础”的要求。其中集合、并发、JVM三个板块占比最高,加起来超过一半,这跟实际面试的考察频率是一致的。
如果你手里也有一份类似题库,我建议你做的第一件事不是开始背题,而是先对照这个分类框架自查一遍,看自己哪个板块最薄弱。比如你集合框架题能答对八成,但并发编程题一遇状态机就头大,那你的复习优先级就很清楚——先攻并发,而不是从头到尾平均用力。这份122道题的价值不在于“多”,而在于让你通过它完成一次完整的知识体系扫描。
2. 核心考点拆解之一:JVM内存与类加载机制
2.1 运行时数据区域:别背八股,要能画图
JVM内存模型是基础题里的重头戏,几乎必考。但很多人背得滚瓜烂熟却经不起追问。面试官问“Java内存区域怎么划分”,你要是只答出“堆、栈、方法区”三个词,大概率会被追问:程序计数器是干什么的?本地方法栈和虚拟机栈什么区别?直接内存属于JVM内存吗?
我记得刚入行时也被问懵过。后来我自己画了不下五遍内存结构图,才真正把这块理清楚。堆存放对象实例,虚拟机栈存放栈帧(局部变量表、操作数栈、动态链接、方法出口),方法区在JDK 8里换成了元空间(Metaspace),使用的是本地内存。程序计数器是线程私有的,记录当前线程执行的字节码行号,只有执行native方法时才是未定义的。这些细节你得能边画图边讲,而不是靠背诵。
2.2 垃圾收集与常用回收器
GC这块是JVM考点的第二座大山。基础题一般会问到“如何判断对象可以被回收”“常见的垃圾收集器有哪些”。判断对象是否可回收,主流答案就是可达性分析,从GC Roots出发遍历对象图,不可达的对象标记为可回收。GC Roots包括虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、本地方法栈中JNI引用的对象。
收藏器方面,面试高频是CMS和G1的区别,以及JDK 11之后默认的ZGC。CMS的缺点是碎片化和并发阶段CPU敏感,G1用Region划分堆内存,可预测停顿时间。你要是能主动提一句“G1在JDK 9以后成为默认垃圾收集器”,面试官会认为你有持续跟进的习惯。另外需要记住的是:对象分配优先在Eden区,大对象直接进老年代,-Xms和-Xmx建议设为相同值避免堆扩容抖动——这些细节都是我实际排查线上问题时验证过的。
2.3 类加载机制和双亲委派模型
类加载机制也是基础题常客,典型问法是“什么是双亲委派模型”“为什么需要双亲委派”。回答这个问题不要只说“先让父加载器加载,不行自己再加载”,要讲清楚这样做的目的:防止核心类库被篡改。比如你自定义一个java.lang.String,如果不走双亲委派,应用加载器先加载了你的类,那整个JVM的字符串操作就乱了。双亲委派保证核心类库只能由引导类加载器加载。
这里建议大家补充一个知识点:类加载的三个过程是加载、连接(验证、准备、解析)、初始化。很多人会把“加载”和“初始化”混为一谈。面试官问到“静态代码块什么时候执行”,理解类初始化阶段才能真正答对。还有一点,打破双亲委派的典型场景是Tomcat的WebAppClassLoader,它为了每个Web应用隔离jar包,会优先自己加载,这是面试加分项。
2.4 OOM场景与排查思路
基础题考OOM通常会问“哪些区域可能发生OutOfMemoryError”。堆内存不足是最常见的,对应java.lang.OutOfMemoryError: Java heap space。元空间不足会报Metaspace错误。栈溢出则是StackOverflowError,深层递归调用触发。除了说出这些错误类型,更重要的是说出排查思路——用jmap dump堆转储,配合MAT或者JProfiler分析大对象,然后定位到代码行。
我自己排查过的一个典型案例:一个定时任务每天批量处理数据时经常OOM,dump下来一看是某个工具类里静态Map缓存了全量用户数据,只增不减。这种问题靠调大堆内存是治标不治本,真正的解法是换成带过期策略的本地缓存或引入Redis。把这个案例讲给面试官听,比单纯背诵“堆内存溢出可以调大-Xmx”有说服力得多。
3. 核心考点拆解之二:集合框架里的数据结构博弈
3.1 HashMap:从put到扩容的完整链路
HashMap是Java面试的神级考点,没有之一。我这份122道题里,光是围绕HashMap展开的变体题就有十几道。最核心的主线问题就是:一个key-value被put进HashMap之后,到底经历了什么。正确流程是:先对key计算hash值,做一次高低位异或扰动,然后(n-1)&hash定位到数组下标位置;如果该位置为空,直接放入;如果不为空,遍历链表或红黑树,比对key的equals方法,相同则覆盖,不同则尾插进入。
关于扩容,要记住默认初始容量16、负载因子0.75、单链表长度到8且数组长度到64时转红黑树这三个阈值。为什么是0.75?这是空间和时间的一个折中。为什么树化是8?因为理想情况下hash分布均匀时,链表长度到8的概率极低,还不到千万分之一,所以8是泊松分布的参考值。能把这个数学背景讲出来,你就不只是“背过答案”,是真的理解了设计者的意图。
3.2 ArrayList与LinkedList的取舍之道
ArrayList和LinkedList的对比也是基础题高频。标准答案是:ArrayList基于动态数组,随机访问快,插入删除慢(涉及元素搬移);LinkedList基于双向链表,插入删除快,随机访问慢。但这个答案有个陷阱——实际项目中LinkedList的插入删除“快”是有条件的。如果你在List中间频繁插入删除,LinkedList确实有优势,因为它只需要改前后节点的指针;但如果你是在尾部追加元素,ArrayList通常更快,因为ArrayList尾插是直接赋值,摊销时间复杂度O(1),而LinkedList每次都要new一个节点。
另一个衍生考点是ArrayList的扩容机制。默认初始容量10,每次扩容为原来的1.5倍(oldCapacity + (oldCapacity >> 1)),扩容后需要把旧数组复制到新数组,这是典型的空间换时间取舍。我面试时会追问“如果在循环里反复add十万个元素,会扩容多少次”,很多人答不上来。算一下:容量序列是10、15、22、33、49……大概会扩容17次左右,每次都有数组拷贝开销。这也是为什么能预估数据量时,建议直接new ArrayList<>(expectedSize)的原因。
3.3 集合类的线程安全与并发容器
集合线程安全这块,我会把题目的层次拉开。基础题会问“ArrayList是线程安全的吗”,然后延伸到Vector和Collections.synchronizedList、CopyOnWriteArrayList这三者的关系。Vector是JDK 1.0的老古董,方法加了synchronized,但性能差。Collections.synchronizedList通过包装器给每个方法加锁,但复合操作仍然不安全。CopyOnWriteArrayList是读写分离思路:读不加锁,写时复制一份新数组,适合读多写少的场景。
还有一个高频点是ConcurrentHashMap。面试官喜欢问“ConcurrentHashMap为什么比Hashtable效率高”,你要能答出:Hashtable是锁整个表,而ConcurrentHashMap在JDK 7用Segment分段锁,JDK 8改为CAS加synchronized锁链表头节点,锁粒度更细,并发度更高。能讲出JDK 7到JDK 8的变化,说明你关注过演进,基础题的加分项往往就在这些细节里。
4. 核心考点拆解之三:并发编程的原子性、可见性、有序性
4.1 线程创建方式与生命周期
并发编程在Java基础面试里占据绝对主力位置。最基础的题目是“创建线程有几种方式”。标准答案是四种:继承Thread类、实现Runnable接口、实现Callable接口配合FutureTask、使用线程池。但只答这四种只能算及格,要加分就得接着说:继承Thread和实现Runnable本质上没有区别,因为Thread本身也实现了Runnable;Callable跟Runnable比多了返回值,可以抛出受检异常;线程池方式本质上还是前三种的封装,但好处是线程复用,避免频繁创建销毁。
线程的状态是另一个必考点:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。我面试时会让候选人画状态转换图,重点看两个地方:一是wait()进入WAITING后,notify()唤醒进入的是Runnable还是Blocked?正确答案是Runnable;二是sleep()和wait()的区别,sleep不释放锁,wait释放锁——这个问题不知道绊倒了多少人。
4.2 synchronized与Lock的底层博弈
synchronized和ReentrantLock的对比属于理解层题目,光答“一个是关键字一个是类”肯定不行。要往深了讲:synchronized经过JDK 6的锁升级(偏向锁→轻量级锁→重量级锁)后,性能已经不输Lock,大多数场景下官方都推荐用synchronized。但Lock有自己的独门绝技:可中断锁、可超时获取锁(tryLock(timeout))、公平锁、多个条件队列(Condition),这些是synchronized不具备的。
还有他们的底层原理,synchronized基于Monitor监视器锁,通过字节码指令monitorenter和monitorexit实现。ReentrantLock基于AQS(AbstractQueuedSynchronizer),通过volatile的state变量加CLH队列实现。面试官如果追到AQS级别,你至少要把“独占模式”“共享模式”“state状态的CAS操作”这三个关键词答出来。这块内容偏深,但基础题里出现它的概率并不低,值得好好准备。
4.3 volatile、CAS与AQS
volatile是并发基础题里最容易“答而不懂”的一个点。很多人能背出“保证可见性,不保证原子性”,但一问“为什么不能保证原子性”就卡住了。volatile的语义是:写一个volatile变量会立即刷新到主内存,读一个volatile变量会从主内存重新读取,这就是可见性;但它没有锁,对count++这种读改写操作,多个线程的指令仍然会交错执行,所以不保证原子性。还有一个考点是volatile会禁止指令重排,也就是有序性,典型应用是单例模式的双重检查锁中的volatile修饰单例对象。
CAS是并发编程的基石,全称Compare And Swap,比较并交换。你要能解释它的三个操作数:内存位置V、预期原值A、新值B,只有V的值等于A时才用B更新V,否则什么都不做。CAS最大的缺点是ABA问题,解决思路是加上版本号,Java里AtomicStampedReference就是干这个的。面试时主动提ABA问题,会让面试官觉得你的并发基础不是背出来的。
4.4 线程池的七大参数与拒绝策略
线程池是应用题的高频考法。基础题一般是问“线程池有哪些参数”,但我会建议你把七个参数背到滚瓜烂熟:核心线程数、最大线程数、空闲线程存活时间、存活时间单位、任务队列、线程工厂、拒绝策略。然后要能讲出执行流程:提交任务后,先判断核心线程是否满,没满就创建线程执行;满了就进队列等待;队列满了再判断最大线程数,没满就创建临时线程;都满了就执行拒绝策略。
拒绝策略有四种:AbortPolicy直接抛异常(默认)、CallerRunsPolicy调用者线程自己执行、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃最旧的任务。这里我要提醒一个实战经验:线上环境建议自定义拒绝策略,至少要把任务信息打日志,否则用DiscardPolicy,任务被丢了你都不知道。还有核心线程数怎么设,CPU密集型的设为CPU核数+1,IO密集型的设为2 * CPU核数,这是经验值,但真实场景还要结合压测来调。
5. 核心考点拆解之四:异常、反射、IO与泛型的隐藏联系
5.1 异常体系:受检异常与非受检异常怎么区分
异常在基础题里出现频率不算最高,但属于送分送得最干脆的模块。Java的异常体系以Throwable为根,下面分Error和Exception。Error是JVM层面的严重问题,比如OutOfMemoryError、StackOverflowError,程序无法处理也不应该处理。Exception下面又分受检异常(Checked Exception)和非受检异常(RuntimeException)。
受检异常必须在代码里显式捕获或抛出,比如IOException、SQLException,编译期就强制检查。非受检异常包括NullPointerException、ArithmeticException、ClassCastException等,编译期不强制处理。面试官喜欢问“什么时候用受检、什么时候用非受检”,我的经验是:调用方能合理恢复的用受检,比如文件不存在可以提示用户换个路径;而编程错误导致的用非受检,比如空指针和数组越界,这些应该通过修代码解决。还有try-with-resources这个JDK 7特性,能自动关闭实现了AutoCloseable的资源,比如流和数据库连接,这也是一个容易考到的细节。
5.2 反射机制的经典应用场景
反射是Java动态特性的基石,也是很多框架底层的核心。基础题问法是“什么是反射,能做什么”,进阶一点会问“反射有哪些应用场景”。标准答案是:在运行时获取类的完整结构信息(类名、方法、字段、注解),并可以动态调用方法和操作属性。坏处也要能说出来:性能开销大(比直接调用慢几个数量级)、破坏封装性、增加代码复杂度。
反射在现实中的经典应用包括:Spring的IOC容器通过反射创建Bean实例,MyBatis通过反射把数据库字段映射到实体类属性,还有各种ORM框架。如果你想在答案里加点“有经验”的东西,就说反射配合注解做字段校验器——这是很多公司实际在做的事情。我做过一个项目,就是定义@NotNull、@Length(max = 50)这类注解,然后用反射扫描实体字段做参数校验,框架代码量很小但全项目通用,这就是反射能力的最佳展示。
5.3 泛型擦除:为什么运行时拿不到List<String>
泛型是Java面试里比较“烧脑”的基础考点。核心知识点是:Java的泛型是编译期类型检查,运行时泛型信息会被擦除。所以List<String>和List<Integer>在运行时的Class对象是同一个ArrayList.class,你不能通过反射拿到List<String>的String这个类型参数。这个设计是为了兼容JDK 1.5之前的老代码。
泛型擦除带来的常见面试题包括:为什么不能创建泛型数组(new T[10]是不合法的),为什么静态方法不能访问类级泛型参数,以及什么是通配符上下界。上下界这个点经常考:? extends T是上界通配符,表示T或T的子类,只能读不能写;? super T是下界通配符,表示T或T的父类,只能写不能读。记住口诀“PECS:Producer Extends,Consumer Super”,这是Effective Java里的经典总结。
5.4 IO模型:BIO、NIO、AIO到底是什么
IO这块基础题喜欢问“BIO、NIO、AIO的区别”。BIO是同步阻塞IO,一个连接一个线程,连接空闲时线程也在阻塞等待,资源浪费严重。NIO是同步非阻塞IO,基于通道和缓冲区,配合Selector选择器可以用一个线程管理多个连接。AIO是异步非阻塞IO,发起IO操作后直接返回,完成后由操作系统通知回调。
面试官在这里最想听到的不是三个缩写,而是你用过什么。多数候选人会聊Netty,这没问题,但你要能讲出NIO的三件套:Channel(通道,双向的)、Buffer(缓冲区,读写都要经过它)、Selector(选择器,注册感兴趣的事件,轮询就绪事件)。再深入一点,IO多路复用有select、poll、epoll三档演进,epoll用红黑树加事件驱动,是Linux下性能最高的模型。能把这个链路讲清楚,说明你不只是背概念,是真的理解。
6. 122道题之外的备考建议与踩坑记录
6.1 刷题的正确姿势:先分类、再输出、后复盘
一份122道题的清单拿在手里,最忌讳的是从头到尾背一遍。我推荐的刷法分三步。第一步是分类,先按我前面说的七个板块把题目归类,统计每个板块的题量,找出自己的薄弱区。第二步是输出,每道题别只在脑子里过,要开口讲出来,或者用文字写一遍。输出过程会逼你把模糊的地方暴露出来,讲不顺畅的地方就是你还没真正理解的地方。第三步是复盘,隔两三天重新讲一遍错题和卡壳题,直到流畅为止。
我见过太多人刷题的方式是“看答案,记住了,下一道”,这就是无效刷题。你面试时面对的是动态追问,不是按原题背诵,如果没把原理内化成自己的表达,换个问法就露馅。我建议你每道题准备30秒到一分钟的版本,再准备一个三分钟的扩展版本,前者用于快答,后者用于应对追问,双版本打法反而能帮你把知识点理得更透。
6.2 基础题和框架题的时间怎么安排
看热词就能发现,现在Java面试题里MyBatis、Redis、Kafka、Spring Boot这些框架和中间件占比非常高。很多人就慌了,觉得基础题是不是没那么重要了。我的观点是:基础题和框架题不是二选一,而是前后关系。框架本身就是建立在基础之上的,MyBatis的Mapper代理用到了动态代理机制,Spring的声明式事务用到了动态代理和AOP,Redis分布式锁的原子性问题用到了Java并发知识,Kafka消费者组的再平衡也和线程模型有关。
我给一个时间配比建议:如果距离面试还有一个月,前两周主攻本文章覆盖的Java基础核心,第三周把集合、并发、JVM三大块结合源码做深度阅读,最后一周围绕MyBatis、Redis、Spring Boot做结合,每个框架顺手准备几个“底层原理”问题。热词里还有Linux、SQL、前端Vue的面试题,这些通常是不同岗位的要求,如果你投的是Java后端开发,主战场还是Java基础和中间件,Linux和SQL可以安排为辅助内容,但不能反客为主。
6.3 高频失分点与面试表达技巧
最后分享几个真实的失分点。第一个是“答案太长没有重点”,一道满分为止的简述题硬是讲成五分钟的演讲。面试官追问时再往深里展开,不追问就果断收住,这是表达节奏问题。第二个是“不加限定地使用绝对化词语”,比如“线程池一定是这个参数”“G1一定比CMS好”,工程上很少有绝对,使用“大多数场景”“默认情况下”这种限定词,显得你更有实践经验。
第三个失分点是“只答是什么,不答为什么”。举例来说,面试官问“HashMap默认容量为什么是16”,你要是只说“不多不少就是为了均匀分布”,就不够;能补充“16是2的4次方,方便hash值通过位运算取模,同时兼顾了内存占用和hash散列效果”,才算答到位。第四个容易翻车的是“把别人博客的结论当自己的经验”,比如网上说“LinkedList插入比ArrayList快”就直接背,我前面已经说了,这个结论是有前提的,你得能区分场景。
我在实际面试别人的过程中,最看重的就是“能不能把原理讲得让人听懂”。一道Java基础题,候选人能结合到自己的项目场景,哪怕场景很小,也比把源码默写一遍有说服力。所以这份122道题的最终用法,不应该是背完就完,而是每道题都去想想:我之前的项目里哪里遇到了这个问题,我当时的处理方式是什么,现在回头看有没有更好的方案。把题库当成一面镜子,照出自己的短板和盲区,这才是它最大的价值。