【面经】5年Java面试亲身经验
一、背景
自己由于某种原因,需要找下份工作,经历了2周多时间面了20多家(按部门算),其中包括AT, TMDK等大厂,也拿到了一些offer。
整个过程特别紧凑,比较辛苦,加上裸面原因,前期表现的并不好,到后面才娴熟了一些。
因为这样连贯的面试车轮战,很容易就能感受到面试的高频知识点,对后面的面试就越来越能把握住节奏点。
写这篇文章的目的,除了自我总结之外,也希望能对后面的年轻人有一些帮助。
二、题目列表:
每家1-3轮不等,题目太多,只能凭印象或当时做的笔记列出,其中高频的(出现3次及以上)会以红色标出。
备注:因为时间有限,对每个题目下的答案,仅仅是列出了脑海中的“记忆索引”,仅供参考(水平有限甚至可能还有错误),需要自行去学习去总结答案。
1. HashMap数据结构?put, get过程?扩容机制?JDK1.8版本中相比1.7做了哪些优化?容量怎么计算的?
hash表+链表+红黑树;先计算hashcode,再对&(n-1)计算,最后使用equals判断;double容量扩容,并进行rehash计算,性能开销较大;增加了红黑树,链表插入由头插该为尾插(为了解决并发场景CPU 100%问题);直接由size属性返回
建议:深度分析源码,重点关注核心方法
2. ConcurrentHashMap数据结构?如何保证线程安全的?相比1.7做了什么优化?
与hashmap结构一致;使用cas保证;使用cas代替segement分段锁,提升效率
3. 为什么String被设计成不可变的?
设计考虑、效率考虑、安全考虑
4. 限流算法有哪些?区别是什么?令牌桶如何实现?
漏桶与令牌桶;前者保持匀速,后者能应对突发流量;使用guava的RateLimiter
5. 一致性hash算法?
已知一个[0, 2^32-1]这样整数的集合,从小到大按顺时针均匀分布。先将机器ip(或hostname等其他唯一信息)使用某种hash算法,计算出hash值,
再对2^32取余,定位到环中的某个位置。再对key进行hash计算,对2^32取余,定位到某个位置后,按顺时针找到第一个机器的位置,这样就找到了key与机器的映射关系。
为了解决机器数量过小,会有数据不平衡的问题,可以再建立虚拟节点,如针对172.23.23.23建立172.23.23.23#1, 172.23.23.23#2, 172.23.23.23#3 这样3个虚拟节点,再做同样的操作
6. JVM内存模型(有些面试官其实只是让你回答内存布局)?各个内存区域分别是做什么的?有哪些是共享的?
每个线程有独立的内存,互相是隔离的,读变量时会从主内存读取放到线程内存中,做完计算后,会“异步”的写回主内存;
虚拟机栈用于保存变量引用,此处省略...
建议:JMM这块知识,有很多细节,涉及happens-before原则,内存屏障等很多概念的东西,能理解就去理解下
7. 年轻代,老年代区别?垃圾回收算法分别是?垃圾回收器有哪些?
前者保存小或生命周期短的对象,后者保存大或生命周期长的对象;前者是复制算法,后者是标记清除或标记整理算法;
一共有7个垃圾回收器,注意ParNew和CMS是搭配组合,省略...
8. CMS具体的工作流程?G1与CMS区别?
大致有4个步骤:初始标记->并发标记->重新标记->并发清除,第1和3步会STW(stop the world),这个回收器是以追求响应时间短为目标;
前者有产生浮动垃圾,空间碎片等缺点,后者将堆分为很多个region,不再具有特定的年轻代,老年代,GC时会自适应调整策略,更加智能,也是追求响应时间短为目标。
9. GC Root对象有哪些?
栈中变量引用的对象;方法区中静态属性、常量应用的对象;JNI中引用的对象
10. 强引用、软引用?弱应用?虚引用?
被视为存活对象不会被GC;SoftReference,只有在OOM之前才被GC掉;WeakReference,每次GC时都被GC掉;省略..
11. 类加载机制?tomcat为什么打破双亲委派?
默认有3个类加载器,系统、扩展、应用类加载器,使用双亲委派模型, 即每次优先让父类加载去加载;tomcat可以同时部署多个应用,安全考虑
建议:注意ClassLoader中loadClass,findClass区别
12. 线程池有哪些?一般怎么用?这些参数的意义是什么?
默认的有4个,fixed, single, cached, scheduled;不过一般用ThreadPoolExecutor, 包含core, max, alive, blockingQueue, threadFactory, abortPolicy,省略...
建议:这个问题是最最最常问的,建议分析下ThreadPoolExecutor源码,洞察内部实现,就不用死记参数之间的联系了。
13. 线程池的线程数一般设定多少?
由机器CPU的core数n和并发任务的属性决定,若任务是CPU密集型,建议设定n+1数量,若任务是IO密集型,建议设定2n数量
14. 线程通信的方式?
volatile, wait/notify/notifyAll, condtion#await/singal等
15. Thread有几种状态?
NEW,RUNNING, BLOCKED, WAITING, TIMED_WAITING,TERMINATED
建议:这些状态定义在Thread类本身;需要注意每个状态是由哪些方法调用产生的;另外操作系统下的线程本身也有自己的状态,比JDK定义的更精简一些,可以自行去对比分析理解
16. sleep与wait区别
前者不会释放锁,后者会释放锁,两者都不会占用CPU资源
17. volatile作用?原理?
可见性,有序性(防止重排序);使用内存屏障
建议:内部原理实现比较复杂,涉及CPU lock指令,一致性协议等,可自行深入了解
18. ThreadLocal如何实现?
Thread类本身有ThreadLoalMap属性,所以每个线程都有独立的map数据
19.索引类型?
一般回答角度:普通索引,唯一索引,主键索引,联合索引
(有时会问让从索引结构角度):hash索引,B+树索引
20. 为什么使用B+树?而不是用其它二叉树或B-树?B+树与B-树区别?
前提知识:数据库是文件存储系统,瓶颈在IO访问次数上,树越高,访问的次数就越多。
B+树是多叉树,相同节点要比二叉树高度低很多,效率会高很多;
相比B-树,主要有2点优势,1. 叶子节点有指针相连,做全表扫描时或范围查询时会很快。2. 内部节点不存储数据,只存储索引值,相同数据页存储的节点信息更多,IO次数也会减少很多,效率就高很多。
21. 聚集索引与非聚集索引区别?
在InnoDB中前者也称主键索引,内部节点存储主键值,叶子节点存储数据行。后者是内部节点存储索引值,叶子节点只存储主键值
22. Innodb与Myisam引擎区别?
前者支持事务,后者不支持。前者是聚集索引,后者是非聚集索引
23. 数据库主从复制过程?binlog日志模式有哪些?
master开启binlog日志开关,slave节点会有线程去读取binlog,然后写回到relay log中,再有线程去读取relay log,写到slave节点中;
statement level记录操作语句, row level记录操作涉及的所有行数据, mixed level;
24. redolog, undolog区别?
前者是可以恢复被中断的事务数据,从而继续提交;后者是可以让事务回滚到上一个版本。
25. 如果事务未提交,意外宕机?如何恢复?
使用redolog或者undolog恢复。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。
26. SQL优化技巧(慢查询解决方案)
使用explain分析执行计划报告,避免一些索引使用不当的问题,或有没有必要建立新的索引,另外可以使用缓存,分库分表解决。
建议:总结出索引使用不当的case,如索引字段参与计算被写在sql中等
27. 什么是索引覆盖?
在InnoDB中,select语句中select出来的字段,只包含索引值和主键值,不用回表(再次根据主键值去查询主键索引,找到数据行)
28. 深层次分页如何优化?比如 limit 10000, 100这种?
假设主键是有序的,先找到100页的主键值a,再根据>a这个条件,再次分页100,又找到主键值b,再根据>b这个条件省略...
建议:这次面试被问到至少3次,前几年面试真没遇到过;描述的不精确,可自行去度娘学习
29. spring如何解决循环依赖?
30. springboot相比spring的优势?
约定大于配置;自启动一些组件
31. @Conditional作用
当某个bean的初始化依赖某个class时,可以使用此注解
32. redis常用数据结构?
list, string, hash, set, zset
33. 持久化策略?aof与rdb区别?
有aof和rdb;前者是持久化操作指令,且追加写入,持久化动作快,但恢复慢。后者是持久化当前全量数据,持久化动作慢,但恢复快。
建议:其实还有混合持久化策略,即aof+rdb;aof与rdb的持久化原理也可去深入了解下,有时可能会被问到
35. 过期key的删除策略?
主动删除,redis内部有定时去监测key的ttl是否过期;被动删除,只有在get时会判断是否过期
36. redis哨兵模式如何保证高可用?
建议:看有没有用过吧,可以自行研究
37. 对于redis-cluster,如何保证高可用的?数据是如何存储的?内部节点如何通信?
多master水平分布,每个master背后有slave做备用节点;redis-cluster,默认会有1.6W多个slot,每个key的hash值对slot总量进行取余,定位到某个slot上,slot是分布在某个master上的;
master-master,master-slave,slave-slave都会依赖同一种二进制协议通信,传达节点信息是否正常,存储的slot号有哪些,都会告诉其它节点,这样每个master都知道了slot号与master之间的映射关系。
建议:看有没有用过吧,可以自行研究
39. 事务隔离级别有哪些?mysql默认是哪个?是如何实现可重复读的?是如何解决幻读问题的?
读未提交,读已提交,可重复读,可串行化;默认是可重复读,RR;MVCC为表新增2个虚拟字段,创建行的版本,删除行的版本,每次查询时都是要小于等于创建版本,所以即使其它事务变更该行,也不会影响当前事务的查询;使用间隙锁
建议:这些问题特别特别被经常问到。这块知识涉及当前读,快照读,X锁, S锁,间隙锁,undolog等等,建议系统去深入理解总结
40. 分布式锁怎么实现的?redis锁怎么防止被误释放?怎么防止死锁?过期时间怎么设置?怎么续约?
对于redis实现,获取锁使用setNx命令,设置key,value,expire;可在value中设置UUID,释放锁时,通过lua脚本判断UUID是否一样,再通过redis的evl命令执行这个脚本;
通过设置过期时间防止死锁;预估一个过期时间后,可以有一个“看门狗”的线程去检测锁的状态,去自动续约。
建议:这块需要去系统深入了解下,特别常见。
41. mysql层面乐观锁、悲观锁实现?
前者加version,在操作sql语句中增加version判断大小的where条件;select ... for update;
42. 什么是cas,有哪些缺点?
compare and set是原子命令;cpu资源开销,ABA问题等
43. AQS大致机制?
主要有2个核心的属性:volatile int state,用于标识锁的状态;双向同步队列,用于保存等待状态的线程。另外使用cas去获取锁/释放锁
建议:去深入源码了解其机制;注意volatile+cas的使用机制,原子类也是这样实现的
44. Synchroinzed与ReentrantLock区别?
使用方式角度:前者可修饰属性、方法、代码块,后者只能是代码块;
其它角度:前者只能是非公平的,后者可支持公平锁。前者不能响应等待中断,后者是能响应等待中断的。
建议:此问题几乎必问
45. Synchronized是如何实现的? 锁优化过程?
每个对象都有一个对象锁,保存在对象头中;先由偏向锁,再升级为轻量级锁,最后是重量级锁
建议:特别容易被问到,建议去深入学习
46. ReentrantLock怎么实现的?
继承AbstractQueuedSynchronizer类,去实现。即基于AQS。
45. wait使用时注意哪些?
要被while循环包住,放在synchroinzed下
46. 场景题汇总:
1) 延时队列怎么实现的?
建议:参考DelayQueue实现
2) 如何应对未来可能的突发流量?
可从限流、MQ削峰、动态水平扩容等角度去展开讲
建议:方案有很多,要实现准备好一种能落地的方案
3) 线上故障排查的思路是什么?
4) 缓存穿透如何解决,缓存雪崩如何解决?
建议:会有布隆过滤器做防缓存穿透的方案;其它可自行去了解学习
5) 你觉得项目的挑战点/难点/亮点是什么?
建议:要实现准备好,列出个1,2,3,4条出来
6). 动态展示积分排行?zset结构如何实现的?
使用redis的zset结构;跳跃表?
建议:需了解zset大致实现,很容易被追问
7). GC调优经验?GC线上问题排查?(如内存泄漏)
暂无;jmap出快照,下载到本地后,用mat去分析
建议:根据自己经验+度娘;
8) 如何排查CPU过高问题?
找到进程pid,top -H -p pid(得出占用CPU资源高到低的线程信息),printf %x id(拿到线程号去转16进制),再从jstack出的线程栈中去找这个线程号,最后定位到代码
9). A线程依赖B,B依赖C,怎么实现?
使用wait/notify/notifyAll
10). 待补充...
47. 算法题汇总:
1) 两个升序数组的差集;输入2个数组,返回一个数组
2) 判断一棵树是否是平衡二叉树;输入root节点,输出true/false
3) 已知一个升序的数组,给特定值a,求在数组中小于a的最大值(需最优复杂度);输入数组和a,输出数组中的某个数
4) 给定2个有序的单链表,合并成一个有序的单链表,且去除重复元素;输入2个头结点,输出新链表的头结点
5) 将数字123422,翻译成“拾贰万叁仟肆佰贰拾贰”,即给定任意数字翻译成汉字;输入long型数字,输出字符串
6) 说下实现LRU的思路,手写LRU
建议:优先使用HashMap结构与链表结合的方案,参考LinkedHashMap
7) 用两个栈数据结构实现一个队列的功能,队列具备入队、出队操作功能即可
8) 实现下二分查找
9) 求最大连续子数组之和
10) 实现下插入排序,并说其是否稳定,时间复杂度是什么
11) 实现一个栈(可用JDK中的Stack),要求能额外实现getMin()方法,标识能获取到此栈的最小值
12) 实现层序遍历
13)待补充...
48 待补充...
三、面试技巧:
上面列出的面试遇到的知识点基本都是大路货,随处可见,我们能做的就是尽量去融会贯通,尽量与项目开发实践中相结合,有自己的心得总结。
对于前3年的,可能对基础知识考察会多一些;
对于5年左右甚至更多,可能对项目经验考察更多一些。
在做面试准备时,要根据自身的情况,有侧重点的去准备。
具体一些的话,比如简历怎么写,怎么介绍项目,怎么有针对的准备某块知识等等技巧,后续有空了补上吧。
四、自我总结:
自己这次面试,是临时抱佛脚,“应试”的心态去准备面试,这样肯定是很不好的。
希望以后自己能踏踏实实的,平常多总结,做一个real coder.