
不扯虚的。这几年我筛过不少简历也面过上百个Java候选人有个感受越来越强烈八股文不是没用而是死背没用。真正拉开差距的不是谁能把HashMap的源码背得更完整而是谁能在高压面试现场把一套知识讲成自己的东西。但反过来说完全不准备八股文临场全靠工作经验来编翻车概率也极高。所以我花了前后几个月把自己手头散落的笔记、面别人的记录、被面时记下的问题全部重新过了一遍整理成了一套按面试官出题逻辑编排的背诵版。这份东西不是把网上的面试题抄一遍而是把每个高频考点的面试官想问什么怎么答能拿分答错会死在哪个环节都标了出来。这篇博文就是把整理过程中的核心思路、高频考点、以及那些特别容易踩的坑分享出来希望能给正在准备Java面试的人一个能直接上手的抓手。1. 先聊清楚八股文到底该怎么背才不会白背好多人一提到八股文就摇头觉得面试问的都是工作中用不到的东西。这个观点对了一半。工作中确实不需要手写红黑树但面试官考红黑树考的不是你能不能写出来而是你有没有在遇到HashMap性能问题时能往这个方向想的能力。这是两码事。我整理这套背诵版时给自己定了个原则每个考点必须回答三个问题——面试官为什么问这个最容易被追问的坑在哪怎么用一句话讲明白。我举个例子你就懂了比如最常见的ArrayList和LinkedList的区别网上一搜一堆答案但大多数候选人的回答方式都是ArrayList底层是数组LinkedList底层是双向链表所以ArrayList查询快、增删慢LinkedList增删快、查询慢。这个回答能拿基础分但拿不到加分。面试官几乎一定会追问一句那LinkedList增删真的比ArrayList快吗这一问能答上来的人立刻少一半。正确答案是如果是在中间插入LinkedList确实不需要移动元素但需要遍历找到插入位置时间复杂度还是O(n)而ArrayList虽然要移动元素但它是内存连续的内存拷贝在数据量小时甚至更快。所以LinkedList增删一定快本身就是错的。这类细节就是我在整理时特别标注的追问点。你真正要背的是这些追问点背后的逻辑链而不是表面结论。另外时间分配也很重要。很多人的复习策略是JVM看一个月并发看一个月结果算法题没时间刷基础题也没过一遍。我的建议是分三档第一档是必拿分包括Java基础语法、集合框架、面向对象、异常、泛型这些属于热身题答不上来基本就凉了。第二档是决胜档包括JVM内存模型、垃圾回收、并发编程、Spring核心原理这些才是面试官区分中级和高级的地方。第三档是亮点档包括网络协议、数据库索引、分布式理论、算法手撕题根据目标公司的业务特点选择性准备。这套背诵版的排序逻辑就是按这个三档来分的我在这篇博文里也按这个思路来展开。2. 基础语法和面向对象面试前半小时必须过完的送分题2.1 封装、继承、多态必须能讲出一个完整的例子这部分表面太简单反而很多人栽跟头。面试官问什么是多态你要是只答一个接口多种实现或者父类引用指向子类对象太空了。面试官真正想听的是你能不能在JVM层面解释清楚这个父类引用调用子类方法到底是怎么找到子类方法的。这就是虚方法表、方法分派的考点。我建议你背一个标准回答框架而且一定要配合代码讲。比如class Animal { public void speak() { System.out.println(Animal speaks); } } class Dog extends Animal { Override public void speak() { System.out.println(Dog barks); } } Animal a new Dog(); a.speak(); // 输出 Dog barks讲这段代码时你要能顺嘴说出a的静态类型是Animal实际类型是Doga.speak()在编译期看的是Animal类是否有speak方法没有就编译报错运行期JVM通过方法表找到实际对象Dog类里重写后的speak方法。这样一个简单的例子就把重写、静态类型、动态绑定全部串起来了。2.2 接口和抽象类的本质区别别只会背抽象类可以有构造方法面试经典题接口和抽象类的区别标准答案有三条抽象类可以有构造方法接口不能抽象类可以有成员变量接口默认是public static final一个类只能继承一个抽象类但可以实现多个接口。这三点能背出来但很多人在为什么要有这两种东西这个问题上卡住。面试官往深问一下那你什么时候用抽象类什么时候用接口就露馅了。我的理解框架是抽象类是is-a关系强调的是你是什么接口是has-a能力强调的是你能干什么。举个实际的例子定义一个Bird抽象类里面可以放age、weight这些共有的状态再定义一个Flyable接口飞是一种能力鸟可以飞飞机也可以飞它们并没有继承关系。如果你让一个Airplane类去继承Bird就闹笑话了但它可以实现Flyable。这个例子一讲面试官会觉得你不仅记住了语法规则还理解了设计意图。2.3 equals和hashCode最常见的连环追问现场这个考点几乎场场必问而且面试官特别爱连珠炮两个对象equals相等hashCode一定相等吗反过来呢不重写hashCode会有什么后果标准逻辑链是这样的equals()相等hashCode()必须相等否则HashMap里就找不到这个对象了反过来hashCode()相等equals()不一定相等这是哈希冲突。然后面试官会追问你那HashMap找对象的过程详细讲讲。你看这一题就直接把集合框架的考点也给引出来了。我在背诵版里专门给这类连锁考点标了箭头方便大家用一道题牵扯出一串知识点去复习。3. 集合框架一条主线抓住80%的考点3.1 HashMap原理背会的同学和理解的同学的差别就在这几个追问HashMap是Java面试的题眼几乎100%会考而且考得非常深。我从面试官视角不建议你按源码从上到下背而是建议按数据从put进去到get出来中间经历了什么这条线来串。你可以按下面这个节奏自测一下put(key, value)时先对key的hashCode做一次扰动计算也就是(h key.hashCode()) ^ (h 16)然后根据(n - 1) hash找到桶的位置。如果桶为空直接放Node节点。如果有冲突用链表挂后面链表长度超过8且数组长度超过64时转红黑树。当数组元素个数超过threshold capacity * loadFactor默认是0.75就触发扩容容量翻倍。这一串答下来说明你真看过源码。但面试官还会继续追扩容时链表和红黑树怎么处理这个就细了。链表在扩容时会拆成低位链和高位链通过(e.hash oldCap) 0来判断红黑树在扩容时如果数量小于等于6会退化成链表。这俩细节很多人到这就答不上来了。我的建议是别死背你把JDK8的源码拆开把resize方法认认真真读三遍理解为什么是oldCap而不是oldCap-1这个位运算逻辑自然就记住了。为什么HashMap线程不安全这个问题也要顺便串进来JDK7里扩容时头插法可能造成环形链表死循环JDK8改成尾插法后不会死循环了但put覆盖、size不准确的问题还在。所以并发环境必须用ConcurrentHashMap。3.2 ArrayList和LinkedList别再背错结论了前面提到过这个案例。我再补充一个面试官常用的夺命连环问ArrayList的默认容量是多少什么时候会扩容扩容后变成多少答案默认容量是10当添加第11个元素时触发扩容扩容后变1.5倍也就是15老数组复制到新数组。还有个细节是Arrays.asList()返回的列表底层还是那个数组不能调用add和remove会抛UnsupportedOperationException。这个坑我在实际业务代码里见人踩过不只一次——把asList的返回值当成普通List用运行时直接崩了这类题面试时也经常以这段代码有什么问题的形式出场。3.3 并发容器CopyOnWriteArrayList和ConcurrentLinkedQueue高并发场景下面试官挺喜欢问ArrayList线程不安全那有什么替代方案你至少要能说出CopyOnWriteArrayList和Collections.synchronizedList。然后顺着追问CopyOnWriteArrayList的写操作是怎么保证线程安全的答案是写时复制add时会用ReentrantLock加锁然后复制一个新数组在副本上添加元素改引用指向新数组读操作不加锁直接读。这个方案的优点是读多写少时效率极高缺点是内存占用大、数据可能短暂不一致。你能把这个代价讲出来比单纯背优点强太多。4. JVM与内存从OutOfMemoryError到垃圾回收面试官的一套组合拳4.1 运行时数据区用你写的一段Java代码是怎么活过来的来记忆很多人在JVM这块的死穴是记不住。我教一个笨但有效的办法把自己当成一个class对象从加载到运行一路上会经过哪些区域。首先你被类加载器加载元数据放进方法区JDK8里叫元空间JVM在堆上给你分配好一个对象实例你所属线程执行你里面的方法时局部变量和中间计算结果放在虚拟机栈里每调用一个方法就压一个栈帧方法返回就弹栈如果你是一个正在执行native方法的人你还得经过本地方法栈最后你为了让其他线程看到你指令计数器里的行号一直往前走记录你执行到哪一句字节码。这一套走下来六个区域的角色你就全记住了。4.2 OutOfMemoryError别把所有的内存溢出都叫堆溢出这里必须提热搜词里那个很扎眼的java: outofmemoryerror: insufficient memory。很多人在本地跑IDEA里的小程序遇到这个报错第一反应是堆内存不够加-Xmx但十有八九加完还报。为什么因为JVM不止堆一个内存区域。报错信息里写Java heap space是堆溢出直接写insufficient memory往往是操作系统层面分配不了内存了可能的原因是机器物理内存真的不够了或者进程地址空间被耗尽也可能是你同时开了太多JVM实例把机器的内存全吃了。你本机跑多个服务时尤其容易出现第二种情况不是某一个服务堆不够而是所有服务加起来的占用已经超过物理内存操作系统开始拒绝分配了。我在整理这份背诵版时专门把OOM常见的几个区域和对应报错文案列了个表报错中的关键词内存区域常见姿势Java heap space堆创建了超大对象、内存泄漏导致对象无法回收GC overhead limit exceeded堆GC一直回收不掉回收时间超过98%回收的内存却不足2%Metaspace / PermGen space方法区/元空间反射、动态生成类过多类加载器无法卸载unable to create new native thread虚拟机栈/本地方法栈线程数超过系统上限最常见于每次请求都新建线程的服务Direct buffer memory直接内存NIO的DirectByteBuffer使用过多又未释放这张表大家可以打印下来贴在工位上比看十遍理论都有用。实际排查的时候第一步永远是jmap -heap 进程号先看各个区域的当前占用不要瞎猜然后取一份jmap -dump下来的堆快照用MAT或VisualVM看大对象和引用链。4.3 垃圾回收判断和常用收集器G1到底好在哪判断哪些对象该回收这个问题引用计数法和可达性分析算法要能对比着讲。引用计数的致命缺陷是解决不了循环引用可达性分析靠GC Roots往下遍历能解决这个问题。面试官一般会追问哪些对象可以作为GC Roots答案包括虚拟机栈中引用的对象也就是正在执行的方法里的局部变量、方法区中类静态属性引用的对象、常量引用的对象、本地方法栈中JNI引用的对象。你能把这些答全说明真理解过而不是只背了GCRoots是什么这一句话。收集器部分重点从CMS和G1的对比切入CMS老年代标记清除并发收集低停顿但会产生内存碎片而且浮动垃圾只能留到下一次Full GCG1把堆划分为若干Region在局部做复制回收不会产生碎片还能设置-XX:MaxGCPauseMillis来软性控制停顿时间。JDK9里CMS被废弃默认G1所以现在面试几乎不深挖CMS的细节了但对为什么G1能控制停顿时间这个问题你得能说清楚G1会跟踪每个Region的回收价值和耗时优先回收收益最大的Region这种思路叫垃圾优先。4.4 类加载机制双亲委派不是背个名字就结束的类加载这块的套路我也摸清了先问什么是双亲委派再问为什么要设计成双亲委派最后问怎么打破双亲委派Tomcat怎么做的。完整答题链路是类加载请求先交给父加载器父加载器处理不了再由子加载器自己加载。目的有两个防止核心API库被篡改比如你自己写一个java.lang.String不会被加载进来顶替JDK的String另一个是避免类被重复加载。打破双亲委派的方式是继承ClassLoader类并重写loadClass方法Tomcat为了给每个Web应用提供独立的类库隔离用WebAppClassLoader优先加载Web应用自己的类找不到再交给父加载器。能讲到这个层面面试官就会觉得你不仅背了还研究过。5. 并发编程从关键字到线程池每一环都是夺命题5.1 synchronized和ReentrantLock的对比答得好能直接镇住全场这题是并发部分最值得花时间准备的没有之一。我的答题框架分五层从浅到深可以自己评估第一层语法层面synchronized是关键字自动加锁释放锁ReentrantLock是API需要手动lock和unlock。第二层锁的灵活度ReentrantLock支持公平锁和非公平锁synchronized只有非公平锁。第三层中断响应ReentrantLock的lockInterruptibly()能响应线程中断synchronized不行。第四层条件等待ReentrantLock可以创建多个Condition精准唤醒特定线程synchronized只能配合wait/notify多个等待条件时处理起来很别扭。第五层底层实现synchronized经过锁升级过程无锁→偏向锁→轻量级锁→重量级锁ReentrantLock底层用CASAQS实现。能讲到第五层面试官基本会打高分。尤其现在JDK对synchronized做了大量优化锁升级的过程本身就是一个高频追问点。你可以记住一个生活化类比锁升级就像一个人滑冰——先穿上冰刀无锁/CAS滑得顺就不换鞋滑不起来了先换成轮滑鞋轻量级锁自旋还是不行最后才穿上最笨重但最稳的滑雪靴重量级锁操作系统互斥量。5.2 volatile为什么它能保证可见性却保证不了原子性volatile必考三板斧可见性、有序性、不保证原子性。可见性怎么实现线程修改volatile变量后会强制把工作内存里的最新值刷回主内存读volatile变量时会强制从主内存读取而不是用线程本地缓存。有序性怎么体现volatile变量读写会插入内存屏障禁止指令重排著名的单例双重检查锁就必须依赖volatile。为什么保证不了原子性因为count这种操作在字节码层面是四条指令读、修改、写回volatile只保证每次读都读到最新值但两个线程可能同时读到同一个旧值然后各自加一丢更新。这个读改写三步为什么会有问题的底层逻辑要自己讲一遍光背结论容易在追问中露馅。5.3 线程池七个参数拒绝策略为什么不能用Executors线程池的提问套路也非常固定。先问七大参数这里要特别小心阻塞队列的选择、饱和策略的选择是面试官最爱展开追问的地方。然后会问如果核心线程都在忙队列也满了新任务来了怎么办这就是各种拒绝策略的适用场景AbortPolicy抛异常、CallerRunsPolicy调用者线程自己跑、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃最老任务。我建议把四种策略背熟之后一定要和实际场景挂钩想一遍比如你是做接口调用的肯定不能选DiscardPolicy不然请求悄悄丢了线上问题都没法排查一般建议自定义ThreadFactory并且把拒绝策略写成打日志降级处理。最后面试官会问为什么《阿里巴巴Java开发手册》不让用Executors因为Executors.newFixedThreadPool()默认的队列是LinkedBlockingQueue队列长度无限大瞬间来大量任务时内存直接爆掉newCachedThreadPool()最大线程数是Integer.MAX_VALUE线程数创建过多会导致OOM。这两个坑我都见过真实生产事故所以老实自定义线程池参数永远别偷懒。6. 手撕代码与算法排序是八股的变体但不是死背模板6.1 冒泡排序和快速排序的高频手撕场景面试手撕环节排序算法出现的概率极高。冒泡排序是入门级但面试官考的不只是写出来而是让你优化。纯正的冒泡是双重循环最外层跑n-1趟内层每趟比较相邻两数逆序就交换。但这种写法在数组本身有序时依然要比较n(n-1)/2次时间复杂度O(n²)浪费严重。优化点是加一个标志位swapped如果某一趟遍历下来没有任何交换说明整个数组已经有序直接break。优化后最好情况时间复杂度降到O(n)。这个优化点你要能在两分钟之内写出来public void bubbleSort(int[] arr) { if (arr null || arr.length 2) return; int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) break; } }快速排序是另一个高频题也是最容易写错的一个。核心逻辑是选一个基准值pivot把比它小的放左边比它大的放右边再对左右递归。我见过大量候选人思路完全正确但递归边界处理不好要么死循环要么数组越界原因就在于递归终止条件写错。给你一个我背熟且几乎不出bug的模板public void quickSort(int[] arr, int left, int right) { if (left right) return; // 终止条件一写错就栈溢出 int pivot arr[left]; int i left; int j right; while (i j) { // 从右往左找第一个小于pivot的数 while (i j arr[j] pivot) j--; // 从左往右找第一个大于pivot的数 while (i j arr[i] pivot) i; if (i j) { int tmp arr[i]; arr[i] arr[j]; arr[j] tmp; } } // 把基准值放到正确位置 arr[left] arr[i]; arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }这个写法我是真在面试白板上写过很多次稳定不出错。注意看两个内层while必须带i j的判断不然边界位置就乱了。6.2 二分查找边界条件是分水岭左右闭区间和左闭右开别混着写二分查找在Java面试中也特别常考而且越简单越容易写出bug。最常见的错误是mid (left right) / 2在极端情况下整数溢出正确写法是mid left (right - left) / 2。然后是边界条件我个人建议平时只练一种写法并且形成肌肉记忆用左闭右闭区间也就是[left, right]循环条件是while (left right)判断后更新时left mid 1或right mid - 1。这套写法逻辑自洽不容易记混。我把两种写法的对照也放一下方便大家看清楚区别在哪写法初始区间循环条件更新方式防止死循环的要点左闭右闭[0, n-1]left rightleft mid 1 / right mid - 1区间始终有效左闭右开[0, n)left rightleft mid 1 / right midright mid 不能减一6.3 还有两个必须秒答的小题数组越界和值传递热搜词里出现了java中数组越界异常和java标识符命名规则这两类基础跟手撕代码不直接相关但我强烈建议你花十分钟过一遍。数组越界要能说出异常类名是ArrayIndexOutOfBoundsException它是IndexOutOfBoundsException的子类而IndexOutOfBoundsException是RuntimeException的子类所以编译器不会强制你捕获但运行时崩了非常难排查因为堆栈信息往往只告诉你越界了不告诉你哪个数组。值传递和引用传递这个话题在Java里特别容易吵核心就一句Java只有值传递。谁再说对象是引用传递你可以直接反驳他——传引用时传的其实是引用变量的值拷贝也就是说方法里改引用本身方法外完全不受影响但方法里通过这个引用去修改对象内部状态方法外看得到。改了引用等于把通讯录卡片上的地址擦了重写原卡片当然不受影响而修改地址指向的那个人就是修改对象本身自然两边都看到变化。用这个例子一下就通了。7. Java 8与编程细节Lambda、枚举、聚合操作的实际体验7.1 Lambda表达式面试官会问它到底是什么语法糖Lambda在热搜词里出现了面试考得不算难但会往底层问一下。标准问题Lambda表达式的本质是什么答案函数式接口的匿名实现类的语法糖。函数式接口就是只含一个抽象方法的接口比如Runnable、Comparator、Function这些。你要能随口说出来Lambda表达式的完整写法、简化写法、能捕获哪些变量——final或事实final的局部变量。我用得最多的一个场景是Comparator链式排序这在处理列表时特别香ListUser users getUsers(); users.sort(Comparator.comparing(User::getAge) .thenComparing(User::getName));这一段比我写一堆for循环加匿名内部类Comparator清爽太多了。面试官如果要你对比Lambda和匿名内部类有什么区别你至少要能说出来Lambda不会额外生成class文件方法运行时通过invokedynamic指令生成而匿名内部类会在编译期生成独立的class文件。7.2 枚举类型不只是用来表示常量Java枚举这个考点看着简单其实展开讲可以很深。定义常量用枚举和用public static final int有什么区别标准答案枚举是类型安全的传入一个不存在的枚举值编译器直接报错而int常量你传个任意int进去编译期完全检测不出来。比如你定义一个Status枚举有SUCCESS和FAIL在方法参数里写Status status别人没法传一个3进来但你要用int常量定义我传3你也只能运行时发现不对。另外枚举还可以带字段和构造方法还能定义抽象方法由每个实例分别实现这在实现策略模式时就很好用。我整理过一个简单实例public enum OrderStatus { CREATED(1) { Override public boolean canCancel() { return true; } }, PAID(2) { Override public boolean canCancel() { return false; } }; private final int code; OrderStatus(int code) { this.code code; } public int getCode() { return code; } public abstract boolean canCancel(); }把状态和它对应的行为放在同一个枚举里消除了一大堆if-else代码干净不少面试时讲出来也是加分项。7.3 聚合操作与Stream API别光背名词得说出你用过的场景java聚合这个热搜词对应的就是Java 8里的Stream聚合操作。面试官问你用过Stream吗绝对不是让你说用过就完了他要你说出几个具体的聚合操作以及它们的执行逻辑。我在实际代码里最常用的有几个filter过滤、map转换、collect(Collectors.toList())收集、groupingBy分组、reduce归约。举个例子有一批订单要统计每个用户的总金额MapString, BigDecimal totalByUser orders.stream() .filter(o - o.getStatus() ! OrderStatus.CANCELLED) .collect(Collectors.groupingBy(Order::getUserId, Collectors.mapping(Order::getAmount, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));当你真能在白板上写出这样一行面试官就知道你平时在真实项目中是认真用过Stream的。还有几个容易被追问的坑要提前踩平stream()和parallelStream()的区别并行流用的是ForkJoinPool公共线程池用不好会互相干扰、peek和map的区别peek是中间操作用于调试不会改变元素本身map是转换会返回新元素。8. 工具链与工程环境排查源发行版17、乱码和本地方法找不到的实战备忘8.1源发行版 17 需要目标发行版 17IDEA编译报错的终极解法热搜词里出现了java: 警告: 源发行版 17 需要目标发行版 17这是被问爆的IDEA编译问题。出现这个警告的根本原因是项目编译用的JDK版本和实际运行的JDK版本不一致或者Maven的source/target版本配置和当前JDK版本不匹配。比如你本机装的是JDK8但项目pom.xml里设了properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /propertiesMaven在JDK8上跑就会报这个警告。解决办法有两个方向一是把pom.xml的版本降成和本机JDK一致二是在IDEA里把所有JVM相关的设置统一包括File - Project Structure里的Project SDK、Project language level、Modules的language level还有Settings - Build Tools - Maven - Importing里的JDK for importer。很多人改了pom但还是报错就是因为IDEA里Project Structure的language level还停在旧版本IDEA的language level是独立于pom配置的两处必须同时对齐。我踩过这个坑后形成了一套固定动作pom的source/target、Project SDK、Project language level、Modules language level、Maven JDK五个地方全部检查一遍不会漏。8.2 VSCode运行Java报错乱码从控制台编码到文件编码的排查链路乱码问题在Java开发里太常见了尤其在VSCode里跑Java报错信息全是乱码简直心态爆炸。根源一般有两种控制台输出编码和源码文件编码不一致或者编译期编码和运行期编码不一致。Java源代码文件默认用UTF-8保存但Windows控制台默认可能是GBKSystem.out输出UTF-8的内容给GBK控制台打印自然乱码。解决的命令行参数是-Dfile.encodingUTF-8在VSCode的launch.json里加上vmArgs: -Dfile.encodingUTF-8同时把VSCode的终端编码也调成UTF-8。还有一类更隐蔽的情况你源码文件本身是GBK编码保存的但Maven编译时强制按UTF-8编译这种直接编译报错或者输出乱码需要在pom.xml里通过project.build.sourceEncoding统一指定。排查乱码的思路其实只有一条先确定源码是什么编码、编译时按什么编码、运行时输出用什么编码解码三处统一为UTF-8就解决了90%的乱码问题。8.3 Lombok报错You arent using a compiler supported by lombok热搜词里还出现了一个挺坑的报错java: You arent using a compiler supported by lombok, so lombok will not work。这个报错我见过很多人一脸懵。原因通常是你用的IDEA版本太新IDEA自带的编译器版本超出了Lombok当前版本已知支持的编译器列表。解决办法按优先级分别是更新Lombok依赖到最新版本如果你项目里指定了annotationProcessorPaths确认Lombok在路径里且版本一致在IDEA的Settings - Build - Compiler里把编译方式改成javac而不是用内部的编译器。另外还要确认IDEA里安装了Lombok插件且Enable annotation processing勾选了这个勾选在Settings - Build, Execution, Deployment - Compiler - Annotation Processors里。三个地方检查完基本都能解决。8.4 环境变量配置JAVA_HOME和PATH的为什么比怎么配更值钱最后一个和环境相关的经典问题Java环境变量配置。大多数人只会照着教程配JAVA_HOME和PATH但面试官会问为什么要配JAVA_HOME——因为这个环境变量是很多中间件找JDK的唯一依据比如Tomcat、Maven、Idea的启动脚本都靠JAVA_HOME来定位Java安装目录你只配PATH不配JAVA_HOME命令行能用java但Tomcat直接起不来。另外还有classpath的概念以前的JDK需要手动配置.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jarJDK1.5之后不再必须了但面试官偶尔会问你只要说出来JDK1.5之后默认会从当前目录和rt.jar加载类所以不需要手动配置classpath这个回答就很干脆。9. 最后再分享一点我个人的复习心得整理这份背诵版的过程中我最大的感受是真正有用的八股文整理不是把知识点堆在一起而是把知识点之间的连线画出来。比如你背了HashMap的原理就一定顺带把hashCode和equals、红黑树、线程安全问题、ConcurrentHashMap全都串一遍这种由一个点带出一张网的复习法远比按章节从头到尾翻书高效。我在面试别人时也特别能感受到一个人如果能把两三个知识点自然串联起来讲他的知识体系一定不是临时背出来的。另外强烈建议你对着镜子或者录个音把每个高频问题用口述的方式讲一遍因为在心里默读懂和开口讲出来完全是两码事语速、停顿、逻辑条理都需要练习。祝所有正在准备面试的朋友都能拿到心仪的offer。