ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

2019京东Java春招笔试题复盘:集合、并发、JVM与场景设计考点全解析

2019京东Java春招笔试题复盘:集合、并发、JVM与场景设计考点全解析 前几天整理硬盘里的旧文件夹翻出一份2019年京东春招Java开发类试卷的扫描版。盯着题目看了半天当年那些在笔试前熬夜背的知识点一下子全回来了。说实话京东这套卷子在当年的Java实习招聘里算很有代表性的不偏不怪不靠冷门语法刁难人但对基础原理的追问密度非常大尤其喜欢把集合、并发、JVM这些核心知识揉进代码题和场景题里一起考。如果你正在准备Java面试或者刚开始刷大厂真题这份试卷复盘应该能帮上忙。我会把整张卷子的考点拆开逐个讲清楚出题人想考察什么、标准回答思路是什么、平时该怎么积累。这里面的内容不只是背答案而是帮你建立一套“看到题目就知道对方在考哪个底层原理”的直觉。1. 先从试卷整体看京东到底在考什么1.1 整卷结构题型与分值分布的记忆复盘我印象里这份试卷满分100分考试时间90分钟题量不算大但每一道题都需要想一会儿。整体题型大致是单选题、多选题、简答题、编程题、场景设计题。比例上单选多选加起来大概30分简答20分编程题25分最后一道场景设计题25分。单选的考点集中在Java基础语法、String、面向对象、异常处理、集合类的基本特性这些属于“热身题”但里面埋了不少陷阱。比如字符串用比较、Integer缓存范围、ArrayList扩容机制这些都是经典送命题。多选和简答开始上难度常见的是“列出HashMap在JDK 1.7和JDK 1.8之间有哪些变化”“volatile和synchronized的区别”“说下单例模式的几种写法及线程安全问题”这类题没有标准代码可以背全看你对原理的理解深度。编程题一般有两道一道纯算法一道带业务背景的Java实现。我当时遇到的一道是“实现一个线程安全的LRU缓存要求get和put的时间复杂度都是O(1)”这道题集合、并发、数据结构全考到了。场景设计题则是给一个电商下单场景让你设计高并发下的库存扣减方案这种题明显能看出京东的电商基因。1.2 京东出题的三个明显倾向刷完这套卷子我总结出京东笔试的三个倾向这三个倾向其实也适用于其他大厂的Java开发岗。第一基础语法考得又多又细。选择题里反复出现的基本都是“看着会一选就错”的知识点比如switch能否用String作为参数、final修饰List之后还能不能添加元素、try-catch-finally中return的执行顺序。这类题没有捷径只能靠平时写代码时多留意细节。第二喜欢追问底层原理。以HashMap为例不满足于“它是基于哈希表实现的”这个答案而是会继续追问哈希冲突怎么解决为什么要用红黑树什么时候触发树化扩容过程会重新计算hash吗连环追问下来如果只是背过面试题没有真正翻过源码很快就答不下去了。第三场景设计贯穿始终。京东毕竟是电商平台高并发、缓存、分布式锁、库存一致性这类话题几乎是必考的。场景题没有标准答案考察的是你能否把Java基础知识、数据库知识、中间件知识组合起来形成一个逻辑自洽的解决方案。2. Java基础题看似送分实则全是坑2.1 面向对象与关键字重载、重写、final、static的真实考点Java基础题里面向对象相关内容每次必考。最常见的问法是“重载Overload和重写Override有什么区别”这类题看上去是送分题但很多人回答时只记住了“参数列表不同”和“子类重写父类方法”漏掉了更关键的细节重载是编译期多态重写是运行期多态重写要求方法签名完全一致返回类型可以是子类协变返回类型访问修饰符不能比父类更严格且不能抛出比父类更宽泛的异常。还有final关键字。面试官喜欢结合具体场景问final修饰的变量不能变那final修饰的引用类型呢答案是这个引用不能再指向其他对象但对象内部状态可以修改。所以final List仍然可以执行add操作。很多人在这里栽跟头把“引用不可变”和“对象不可变”搞混了。static也一样。静态方法能不能被重写很多人脱口而出“不能”准确说是“静态方法可以被隐藏但不能被重写”。因为静态方法是属于类的调用时看引用类型而不是实际对象类型。笔试时会给一段代码问输出什么这种情况下最好的办法是记住static方法不参与多态。2.2 字符串与包装类和equals的爱恨纠葛字符串应该是Java基础题里最容易被出题人拿来挖坑的部分。有一道我印象特别深的题String s1 hello; String s2 hello; String s3 new String(hello); System.out.println(s1 s2); System.out.println(s1 s3); System.out.println(s1.equals(s3));第一行输出true因为s1和s2都指向字符串常量池里的同一个对象。第二行输出false因为new String(hello)在堆中新建了一个对象即使内容相同引用也不同。第三行输出true因为equals比较的是内容。这道题背后是字符串常量池、不可变对象、引用比较这几个知识点的综合考察。延伸出来还有StringBuilder和String的拼接区别。笔试里如果出现大量字符串拼接操作正确答案通常是StringBuilder效率更高因为String是不可变的每次拼接都会创建新对象。再看包装类。Integer缓存的范围是-128到127这个范围内的两个Integer用比较会返回true超出范围就会返回false。笔试题经常变着花样考这个比如Integer a 128; Integer b 128; System.out.println(a b);输出false。这里其实涉及自动装箱和Integer.valueOf()的缓存机制。理解了源码里的缓存逻辑这类题再怎么变形都能秒杀。2.3 异常与泛型容易忽略的边角知识点异常体系在大厂笔试里出现频率不低但很多人复习时容易忽略。基础考点包括Throwable是根类下面分Error和ExceptionException又分受检异常和非受检异常RuntimeException及其子类是非受检异常其他都是受检异常。面试时经常给一段代码问“这个方法会不会编译报错为什么”常见陷阱是方法内部捕获了异常但没处理或者子类重写父类方法时抛出了父类没有抛出的受检异常。还需要注意try-with-resources语法因为Java 7之后实现了AutoCloseable的资源可以自动关闭不需要显式写finally这也是面试官爱问的一个点。泛型常考的知识点是类型擦除。ListString和ListInteger在运行时其实是同一个类型因为泛型信息在编译后就被擦除了统一变成原始类型List。笔试里可能出现“以下哪种方式可以创建泛型数组”这种题答案通常是“不能直接创建泛型数组”需要借助通配符或Array.newInstance。理解了擦除机制就不会被这些变形题带偏。3. 集合框架大厂笔试的兵家必争之地3.1 HashMap的实现原理与JDK 1.7/1.8差异集合框架里HashMap的出场率几乎百分之百。不管笔试还是面试它都是必须啃下的硬骨头。先记住HashMap的底层结构JDK 1.7及以前是数组加链表JDK 1.8开始引入红黑树当链表长度超过阈值8并且数组长度大于等于64时链表会转化为红黑树。put操作的完整流程是先对key的hashCode做扰动运算然后通过(n - 1) hash计算桶下标如果当前位置为空直接放入节点如果不为空遍历链表或红黑树找到相同key就替换value否则新增节点插入完成后检查size是否超过阈值超过就扩容。面试官常追问扩容机制。HashMap的默认初始容量是16负载因子是0.75也就是说元素个数达到12时就会触发扩容。扩容时容量翻倍并且原来桶里的元素需要重新计算位置这也是并发环境下HashMap可能形成循环链表、导致CPU 100%的原因之一。在JDK 1.7中存在这个问题JDK 1.8修复了部分场景但依然不建议在多线程环境中使用HashMap。关于“为什么链表长度大于8才转红黑树”官方注释里给过答案符合泊松分布在随机哈希码下链表长度到达8的概率已经非常低。转红黑树是为了防御最坏情况下的哈希碰撞攻击时间负责度从O(n)降到O(logn)。这个解释要在回答里体现出来显得你研究过源码。3.2 ConcurrentHashMap如何保证线程安全HashMap的线程安全替代品是ConcurrentHashMap这道题在京东试卷里几乎必考。关键点是JDK 1.7和JDK 1.8的实现差异很大。JDK 1.7的ConcurrentHashMap采用分段锁设计默认有16个Segment每个Segment相当于一个小HashMap不同Segment之间可以并发写入锁粒度比较粗。JDK 1.8放弃了分段锁改用CAS加synchronized锁的粒度细化到每个桶的头节点并发度更高。还要知道一个细节size()方法在JDK 1.8里先无锁累加几次如果累加时没有其他线程修改就直接返回如果检测到并发修改就加锁重试。这样既保证了准确性又兼顾了性能。这道题如果只回答“ConcurrentHashMap是线程安全的”基本等于没答必须把加锁细节讲清楚。3.3 其他高频集合题ArrayList扩容、HashSet、LinkedHashMapArrayList也是笔试常客。它的默认初始容量是10扩容时每次变为原来的1.5倍。在JDK 1.8中添加元素时会先判断容量是否足够不够就调用grow方法扩容然后通过Arrays.copyOf把原数组复制到新数组。所以频繁往ArrayList开头插入元素效率很低因为涉及大规模元素搬迁。HashSet的底层就是HashMap只是把所有value都指向同一个空对象。LinkedHashMap则是在HashMap的基础上维护了一个双向链表用来保持插入顺序或访问顺序。日常面试里问“怎么用Java实现LRU缓存”最直接的答案就是继承LinkedHashMap重写removeEldestEntry方法。这也是我在试卷编程题里实际用到的思路。4. 并发编程别只会背synchronized和volatile4.1 线程生命周期与sleep、wait的区别并发编程是Java开发岗笔试的分水岭很多人前面基础题拿满分一到并发题就露馅。先看最基础但最容易被问懵的线程生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。注意从Java线程状态角度看没有单独区分操作系统层面的“就绪”和“运行中”都归为RUNNABLE。sleep和wait的区别算经典送命题。sleep是Thread的静态方法调用后线程进入TIMED_WAITING不会释放锁wait是Object的方法调用后线程进入WAITING会释放锁并且只能在同步代码块中使用。很多人把“释放锁”这个关键区别记反了笔试选择题一错就是一分。notify和notifyAll的区别也要清楚。notify随机唤醒一个等待线程notifyAll唤醒所有。生产环境里推荐用notifyAll因为notify可能导致信号丢失。这些知识点如果能在纸上画出状态流转图理解起来会轻松很多。4.2 synchronized与Lock的底层机制synchronized在Java 1.6之后做了大量优化引入了偏向锁、轻量级锁、重量级锁的升级过程。很多人一提synchronized就说悲观锁其实它的锁升级机制已经很灵活无锁状态时先偏向有竞争时升级为轻量级锁竞争再激烈就膨胀为重量级锁。Lock接口的核心实现是ReentrantLock它基于AQSAbstractQueuedSynchronizer实现支持公平锁和非公平锁支持可中断获取锁、超时获取锁也支持多个条件变量。笔试里常让比较synchronized和ReentrantLock答出“后者更灵活前者使用更简单”还不够要说清楚synchronized是JVM层面自动释放锁Lock需要手动加锁解锁并且通常配合try-finally使用。除了基础的锁机制线程池也是必考内容。ThreadPoolExecutor的七个核心参数分别是核心线程数、最大线程数、空闲线程存活时间、存活时间单位、工作队列、线程工厂、拒绝策略。工作队列常见的有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue拒绝策略有抛异常、丢弃、丢弃最旧、调用者运行。很多面试官还会追问一个陷阱“用Executors.newFixedThreadPool有什么问题”答案是它默认使用无界阻塞队列可能导致任务无限堆积最终OOM所以大厂规范里建议手动创建线程池。4.3 volatile、CAS和原子类的关系volatile有两个核心语义可见性和禁止指令重排序。它不能保证原子性这是最常考的坑。比如多个线程同时执行i即使i被volatile修饰结果依然不对因为i不是原子操作。CASCompare And Swap是Java并发包的基础AtomicInteger底层就是通过CAS加上自旋实现的。CAS存在ABA问题解决方案是使用带版本号的AtomicStampedReference。在京东的场景设计题里提到高并发库存扣减时用CAS加版本号的思路要比直接加锁更高效这会让面试官觉得你真的有实战经验。5. JVM与内存八股文里最伤脑筋的部分5.1 运行时数据区域与OOM实战JVM相关的题通常放在笔试后半段因为难度偏高。先要背熟运行时数据区域程序计数器、虚拟机栈、本地方法栈、堆、方法区。其中虚拟机栈是线程私有的堆是线程共享的方法区在JDK 1.8之后被元空间取代。常见OOM类型要和内存区域对应起来。堆内存不足会报java.lang.OutOfMemoryError: Java heap space这是因为创建的对象太多堆无法分配栈深度不够会报StackOverflowError常见于无限递归元空间不足会报Metaspace相关OOM。热词里那个java: outofmemoryerror: insufficient memory本质就是进程向操作系统申请内存时失败可能是堆参数设置不合理也可能是物理内存不够需要结合JVM参数和系统监控一起排查。笔试中常问“如何排查OOM”。思路是先通过jps找到进程ID再用jstat看堆内存使用情况必要时用jmap导出堆dump文件用MAT或VisualVM分析大对象。回答时把这个排查链路说出来会让面试官眼前一亮因为很多人只会说“调大堆内存”。5.2 类加载机制与双亲委派类加载机制里最核心的概念是双亲委派模型。启动类加载器负责加载JDK内部类扩展类加载器负责加载扩展目录下的类应用类加载器负责加载classpath下的类。加载一个类时先让父加载器尝试加载父加载器加载不到才轮到子加载器。双亲委派的好处是保证Java核心类库的安全防止自定义类覆盖java.lang.String。面试中常问“能不能自己写一个java.lang.String”答案是不能因为双亲委派机制会保证启动类加载器先加载JDK自带的String。如果面试官继续追问“如何打破双亲委派”可以提自定义ClassLoader重写loadClass方法比如Tomcat的类加载器就是典型例子。5.3 垃圾回收与常用调优参数垃圾回收需要掌握三个部分判断对象是否存活、垃圾回收算法、垃圾收集器。判断存活的核心是可达性分析从GC Roots出发能沿着引用链到达的对象就是存活的。GC Roots包括虚拟机栈中引用的对象、静态属性引用的对象、方法区中常量引用的对象等。垃圾回收算法主要有标记-清除、标记-复制、标记-整理。不同收集器用了不同组合比如CMS是标记-清除G1是区域化划分加复制。面试常问“CMS和G1的区别”可以答CMS关注低停顿采用标记-清除会产生碎片G1把堆分成多个Region可预测停顿时间并且支持大对象分配JDK 9之后G1成了默认收集器。JVM调优参数也很容易被考到。基础的三件套是-Xms初始堆大小、-Xmx最大堆大小、-Xmn新生代大小。实战经验是把-Xms和-Xmx设为相同值避免堆大小动态伸缩带来的性能抖动。还有一个容易被忽略的参数-XX:HeapDumpOnOutOfMemoryError生产环境一定要加否则OOM时拿不到堆现场排查问题就像盲人摸象。6. MySQL、Redis与场景设计题6.1 索引失效与SQL优化Java岗笔试不会只考Java数据库和中间件必有一席之地。MySQL部分最常考的是索引失效场景。常见情况有对索引列使用函数或计算、隐式类型转换、左模糊查询like %xx、使用OR连接非索引列、联合索引不满足最左前缀原则。回答这类题时要结合执行计划比如用EXPLAIN查看type字段如果看到ALL就是全表扫描需要优化。京东的电商业务里订单表数据量很大索引设计直接决定接口性能所以笔试出现“给订单查询优化SQL”这类题非常正常。优化思路无非是覆盖索引、避免select *、分页优化、合理使用联合索引。6.2 事务隔离级别与MVCC事务的四大特性和隔离级别属于送分题。四个隔离级别分别是读未提交、读已提交、可重复读、串行化。MySQL默认是可重复读这是InnoDB在RR隔离级别下通过MVCC保证了普通读的一致性。MVCC是面试高频点核心机制是隐藏字段DB_TRX_ID、DB_ROLL_PTR加undo log版本链。读操作会生成ReadView通过比较事务ID判断哪个版本可见。读已提交和可重复读的区别在于生成ReadView的时机RC每次快照读都生成新的ReadViewRR只在第一次快照读时生成。把这个机制理清楚很多数据库面试题都能迎刃而解。6.3 Redis缓存穿透、击穿、雪崩场景设计题绕不开缓存三兄弟。缓存穿透指查询一个根本不存在的数据请求直接打到数据库解决办法是缓存空值或使用布隆过滤器。缓存击穿指某个热点key过期瞬间大量请求打到数据库解决办法是互斥锁或热点数据永不过期加逻辑过期。缓存雪崩指大量key同时过期或Redis宕机导致数据库被压垮解决办法是过期时间加随机值、多级缓存、集群高可用。京东的秒杀场景里这三个问题经常组合出现。答场景题时不要只甩概念可以说清楚我会把商品库存预热到Redis用Lua脚本保证扣减库存的原子性同时用MQ异步处理订单避免数据库压力过大。这样回答把缓存、并发、MQ都串起来了整体方案既完整又落地。6.4 秒杀系统设计题怎么答才加分压轴的场景设计题常见题干是“设计一个商品秒杀系统要求不能超卖请求量巨大时如何保证系统稳定”。这类题没有完美答案但答题框架是有套路的。第一步说清流量漏斗前端页面静态化、CDN加速网关层做限流和风控业务层用Redis预扣库存最后异步写数据库。第二步讲防超卖用Redis Lua脚本原子扣减库存扣减成功才生成订单数据库更新库存时加乐观锁条件update stock set count count - 1 where id ? and count 0。第三步聊削峰秒杀请求先入消息队列后端按实际处理能力消费避免瞬时高并发打垮数据库。第四步补充降级和监控依赖服务超时降级监控QPS、Redis命中率、数据库连接池占用率。我当时答的时候特意强调了一个细节库存预扣要在Redis里做但最终一致性要靠数据库保证如果扣减成功但订单创建失败需要回补库存。这个细节能加分因为它暴露了你真的想过极端情况。7. 这套卷子给我的备考方法启发7.1 刷题不在多在于建立知识树复盘这份试卷我最大的感受是大厂笔试考察的知识点其实就那些但同一个知识点可以换无数种考法。与其盲目刷几百道题不如按知识树来复习。以HashMap为核心往上有哈希冲突、扩容、负载因子往下可以延伸到ConcurrentHashMap、HashTable、HashSet右面还能接到equals和hashCode的关系、TreeMap的排序原理。每复习一个点就把它和相邻知识点关联起来形成一张网。我在准备春招的时候每整理一个专题就会画一张知识脑图。脑图不需要多精美关键是强迫自己把零散知识结构化。比如并发编程这个专题从线程状态开始连到锁、CAS、ThreadLocal、线程池、阻塞队列再连到实际使用场景。笔试时看到“ConcurrentHashMap”这个关键词整张图都会在脑子里亮起来答题自然又快又准。7.2 用输出倒逼输入讲给别人听有一个方法我特别推荐把自己当成面试官把每个知识点讲给同学或室友听。讲的过程中你会发现很多地方以为自己懂了一开口就卡壳。卡壳的地方就是薄弱点翻回去查漏补缺远比闷头看书效率高。我第一次尝试讲“JVM垃圾回收”讲了不到三分钟就卡在“什么时候触发Minor GC”上。后来重新翻书补齐了这个缺口再讲时明显流畅很多。这种“输出倒逼输入”的方法本质上是在模拟面试官的连环追问对应对笔试中的简答题和场景题非常有帮助。7.3 避坑清单当年我踩过的几个坑最后分享几个自己踩过的坑希望看到这篇文章的人能避开。考点常见错误正确思路String比较以为equals比较的是引用equals比较内容比较引用Integer比较误以为所有Integer都是同一个对象只有-128到127有缓存HashMap扩容以为扩容只是复制数组要重新计算hash涉及元素迁移线程池用Executors创建固定线程池手动指定参数避免无界队列OOMRedis缓存缓存穿透和击穿分不清穿透查不存在击穿查热点过期索引优化以为索引越多越好索引有维护成本联合索引要注意顺序这套试卷已经是好几年前的了但里面的考点在今天的Java面试中依然高频出现。别指望背几道真题就能过关把基础原理啃透比囤一百份面试题管用得多。我自己后来带实习生发现一个特别明显的规律能把HashMap原理讲清楚的人Java基础通常不会差能在场景题里有条理地抛出方案的人入职后写代码的可靠性也更高。技术面试考察的从来不只是记忆力而是你有没有建立一套完整的、可解释的程序员思维方式。这份2019年的试卷本质上就是在筛选这样的人。
返回列表