前言
我大概我是从去年12月份开始看书学习,到今年的6月份,一直学到看大家的面经基本上百分之90以上都会,我就在5月份开始投简历,边面试边补充基础知识等。也是有些辛苦。终于是在前不久拿到了字节跳动的offer,现在我也来写面经,希望能帮助到大家!
面经
Java基础
0.HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化。
拉链结构,数组+链表,原理是hash找数组,冲突后拉链表,1.8优化为会进化成红黑树提高效率,并且使用2^n来做容量值
引申点:
- equal & hashcode
- 其他地方的hash处理,如redis的hash、集群slot等
- 对hash算法类型的了解(安全哈希和非安全哈希如mermerhash)
- 对hashMap实现的了解:取hashcode,高位运算,低位取模
- 一致性hash(处理了什么问题,在什么场景用到)
- 红黑树简单描述
1.HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小。
在容量到达抵达负载因子*最大容量的时候进行扩容,负载因子的默认值为0.75
2N的原因:
- hash的计算是通过hashcode高低位混合然后和容量的length进行与运算
- 在length=2n的时候,与运算相当于是一个取模操作
- 那么在每次rehash完毕之后mod2N的意义在于要么该元素是在原位置,要么是在最高位偏移多一位的位置,提高效率
引申点:
- ConcurrentHashMap的扩容:1.7分段扩容以及1.8transfer并发协同的扩容
- redis渐进式hash扩容处理
3.HashMap,HashTable,ConcurrentHashMap的区别。
Map线程不安全(没有用任何同步相关的原语),Table安全(直接加syn),Concurrent提供更高并发度的安全(分段锁思想orSyn+Cas)
引申点:
- 对线程安全的定义:如hashmap在1.7前会头插死循环,但是在1.8改善后还是不能叫线程安全,因为没有可见性
- 对锁粒度的思考:在介于map和table之间存在tradeoff之后的均衡解
- Syn和ReentranceLock的区别
- 锁升级
4.极高并发下HashTable和ConcurrentHashMap哪个性能更好,为什么,如何实现的。
分两种情况讨论:
- 极高并发读:并发读的情况下,Table也为读加了锁,没有并发可言,ConcurrentMap读锁并没有加并发,直接可读,若读resize的某个tab为空则转到新tab去读,Node的元素val和指针next都是volatile修饰的,可以保证可见性,所以concurrentMap获胜
- 极高并发写:在并发写的情况下,table也是直接加了Syn做锁,强制串行,并且resize也只能单线程扩容,ConcurrentMap首先对于每个数组都有并发度,其次在resize的时候支持多线程协同,所以concurrentMap获胜
所以整体而言concurrentMap优势在于:
- 读操作基于volatile可见性所以无锁
- 写操作优势在于一是粗粒度的数组锁,二是协同resize
这个问题的思路是先分类讨论然后描述细节最后在下结论
引申点:
- volatile的实现:保证内存可见、禁止指令重排序但无法保证原子性
- java内存模型
- JVM做的并行优化、先行发生原则与指令重排序
- 底层细节的熟悉
5.HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。
1.7前死锁,1.7后线程会获取脏值导致逻辑不可靠
6.java中四种修饰符的限制范围。
public:公用,谁来了都给你用
protected:包内使用,子类也可使用
default:包内使用,子类不可使用
private:自己用
7.Object类中的方法。
waithashcodeequalwaitnotifygetclasstostringnofityallfinalize
引申点:
- wait和sleep区别
- hashcode存在哪儿(对象头里)
- finalize作用:GC前执行,但是不一定能把这个函数跑完
- getClass后能获取什么信息:引申到反射
8.接口和抽象类的区别,注意JDK8的接口可以有实现。
接口:可以imp多个接口,1.7之前不允许实现,1.8后可以实现方法
抽象类:只能继承一个类,抽象类中可以存在默认实现方法
接口的语义是继承该接口的类有该类接口的行为
抽象类的语义是继承该抽象类的类本身就是该抽象类
9.动态代理的两种方式,以及区别。
- CGLIB:其本质是在内存中继承了一个子类,可以代理希望代理的那个类的所有方法
- JDK动态代理:实现InvocationHandler,通过生成一个Proxy来反射调用所有的接口方法
优劣:
- CGLIB:会在内存中多存额外的class信息,对metaspace区的使用有影响,但是性能好,可以访问非接口的方法
- JDK动态代理:本质是生成一个继承所有接口的Proxy来反射调用方法,局限性在于其只能代理接口的方法
引申点:
- Spring的AOP实现以及应用场景
- 反射的开销:检查方法权限,序列化以及匹配入参
- ASM
10.Java序列化的方式。
继承Serializable接口并添加SerializableId(idea有组件可以直接生成),ID实际上是一个版本,标志着序列化的结构是否相同
11.传值和传引用的区别,Java是怎么样的,有没有传值引用。
本质上来讲Java传递的是引用的副本,实际上就是值传递,但是这个值是引用的副本,比如方法A中传入了一个引用ref,那么在其中将ref指向其他对象并不影响在方法A外的ref,因为ref在传入方法A的时候实际上是指向同一个对象的另一个引用,可以称之为ref',ref'若直接修改引用的对象会影响ref,但若ref'指向其他对象则和ref没有关系了
12.一个ArrayList在循环过程中删除,会不会出问题,为什么。
分情况讨论:
- fori删除,不会直接抛异常,但是会产生异常访问
- foreach删除(实际就是迭代器),会直接抛出并发修改异常,因为迭代器会进行获取迭代器时的exceptModCount和真实的modCount的对比
引申点:
- 迭代器实现
- ArrayList内部细节
13.@transactional注解在什么情况下会失效,为什么。
方法A存在该注解,同时被方法B调用,外界调用的是Class.B的方法,因为内部实际上的this.a的调用方式没走代理类所以不会被切面切到
数据结构和算法
1.B+树
出度为m的一颗树,节点的子女在[M/2,M]之间
叶子节点存储全量信息
非叶子节点只充当索引进行叶子节点的路由(内存友好、局部性友好)
底层的叶子节点以链表的形式进行相连(范围查找友好)
2.快速排序,堆排序,插入排序(其实八大排序算法都应该了解
快排:核心是分治logn
堆排:基于二叉树nlogn
插入:暴力n2
3.一致性Hash算法,一致性Hash算法的应用
一致性hash,将整个hash的输出空间当成一个环,环中设立多个节点,每个节点有值,当对象的映射满足上个节点和这个节点中间值的时候它就落到这个节点当中来
应用:redis缓存,好处是平滑的数据迁移和快速的rebalance
引申点:
- 一致性hash热点怎么处理:虚拟节点
- redis如何实现的:客户端寻址
JVM
1.JVM的内存结构。
程序计数器:计算读到第几行了,类似一个游标
方法栈:提供JVM方法执行的栈空间
本地方法栈:提供native方法执行的栈空间
堆:存对象用的,young分eden,s0,s1,分配比例大概是8:1:1,Old只有一个区
方法区:1.8后为metaspace,存class信息,常量池(后迁移到堆中),编译出来的热点代码等
引申点:
- heap什么时候发生溢出
- stack什么时候发生溢出
- 方法区什么时候发生溢出
- hotspot code的机制
- 流量黑洞如何产生的
2.JVM方法栈的工作过程,方法栈和本地方法栈有什么区别。
方法栈是JVM方法使用的,本地方法栈是native方法使用的,在hotspot其实是用一个
3.JVM的栈中引用如何和堆中的对象产生关联。
引用保存地址,直接可以查找到堆上对应地址的对象
4.可以了解一下逃逸分析技术。
方法中开出来的local变量如果在方法体外不存在的话则称之为无法逃逸
- 可以直接分配在栈上,随着栈弹出直接销毁,省GC开销
- 消除所有同步代码,因为本质上就是个单线程执行
引申点:
JVM编译优化:
- 逃逸分析
- 栈上分配
- 分层编译与预热
- 栈上替换
- 常量传播
- 方法内联
...
5.GC的常见算法,CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势。
常见算法:
- 标记清楚:存在内存碎片,降低内存使用效率
- 标记整理:整理可分为复制整理和原地整理,不存在内存碎片,但是需要额外的cpu算力来进行整理,若为复制算法还需要额外的内存空间
CMS流程:
- 初始标记(stw):获得老年代中跟GCRoot以及新生代关联的对象,将其标记为root
- 并发标记:将root标记的对象所关联的对象进行标记
- 重标记:在并发标记阶段,并没有stw,所以会有一些脏对象产生,即标记完毕之后又产生关联对象修改
- 最终标记(stw):最终确定所有没有脏对象的存活对象
- 并发清理:并发的清理所有死亡对象
- Reset:重设程序为下一次FGC做准备
CMS优劣:
-
优点:
- 不像PN以及Serial一样全程需要stw,只需要在两个标记阶段stw即可
- 并发标记、清楚来提升效率,减少stw的时间和整体gc时间
- 在最终标记前通过预设次数的重标记来清理脏页减少stw时间
-
缺点:
- 仍然存在stw
- 基于标记清楚算法的GC,节省算力但是会产生内存碎片
- 并发标记和清楚会造成cpu的高负担
G1流程:
这个我只懂个大概,如下
分块分代回收,可分为youngGC和MixedGC,特点是可预测的GC时间(即所谓的软实时特性)
引申点:
- 是否进行过线上分析
- GC日志是否读过,里面有什么信息
- 你们应用的YGC和FGC频率以及时间是多少
- 你清楚当前应用YGC最多的一般是什么吗
业务相关:
- 在线上大部分curd业务当中,实际上造成ygc影响较严重且可优化的是日志系统
- 对dump出来的堆进行分析的话里面有很大一块是String,而其中大概率会是日志中的各种入参出参
优化方案有很多:
- 将不需要打日志的地方去除全量日志打印功能
- 日志在不同环境分级打印
- 只打出错误状态的日志
- 在大促期间关闭非主要日志打印
- 同步改异步等
6.标记清除和标记整理算法的理解以及优缺点。
上文已答
7.eden survivor区的比例,为什么是这个比例,eden survivor的工作过程。
8:2
定性的来讲:大部分对象都只有极短的存活时间,基本就是函数run到尾就释放了,所以给新晋对象的buffer需要占较多的比例,而s区可以相对小一点来容纳长时间存活的对象,较小的另一个原因是在几次年龄增长后对象会进入老年代
定量的来讲:实验所得,也可以根据自己服务器的情况动态调整(不过笔者没调过)
8.JVM如何判断一个对象是否该被GC,可以视为root的都有哪几种类型。
没有被GCRoot所关联
Root对象:(tips:不用硬记,针对着JVM内存区域来理解即可)
- 函数栈上的引用:包括虚拟机栈和native栈
- static类的引用:存在方法区内
- 常量池中的常量:堆中
引申点:
- gc roots和ref count的区别
9.强软弱虚引用的区别以及GC对他们执行怎样的操作。
强:代码中正常的引用,存在即不会被回收
软:在内存不足的时候会对其进行GC,可用于缓存场景(类似redis淘汰)
弱:当一个对象只有弱引用关联的时候会被下一次GC给回收
虚:又称幽灵引用,基本没啥用,在GC的时候会感知到
引申点:
- 每个引用的使用场景
- 是否在源码或者项目中看到过or使用过这几种引用类型(ThreadLocal里用了WeakReference)
10.Java是否可以GC直接内存。
在GC过程中如果发现堆外内存的Ref被使用则GC
11.Java类加载的过程。
12.双亲委派模型的过程以及优势。
13.常用的JVM调优参数。
14.dump文件的分析。
15.Java有没有主动触发GC的方式(没有)。
多线程
1.Java实现多线程有哪几种方式。
2.Callable和Future的了解。
3.线程池的参数有哪些,在线程池创建一个线程的过程。
4.volitile关键字的作用,原理。
5.synchronized关键字的用法,优缺点。
6.Lock接口有哪些实现类,使用场景是什么。
7.可重入锁的用处及实现原理,写时复制的过程,读写锁,分段锁(ConcurrentHashMap中的segment)
8.悲观锁,乐观锁,优缺点,CAS有什么缺陷,该如何解决。
9.ABC三个线程如何保证顺序执行。
10.线程的状态都有哪些。
11.sleep和wait的区别。
12.notify和notifyall的区别。
13.ThreadLocal的了解,实现原理。
数据库相关
1.常见的数据库优化手段
- log同步刷盘改异步刷盘
- 集群的话强双写改异步同步
- 针对sql优化(explain慢sql)
- 添加索引
2.索引的优缺点,什么字段上建立索引
优点:查的快,支持range
缺点:大部分查询实际需要回表,索引建立会额外消耗内存和磁盘,对开发者的sql也有要求
字段:区分度大的字段
3.数据库连接池。
4.durid的常用配置。
计算机网络
1.TCP,UDP区别。
2.三次握手,四次挥手,为什么要四次挥手。
3.长连接和短连接。
4.连接池适合长连接还是短连接。
设计模式
1.观察者模式
2.代理模式
举例子JDK动态代理,通过一层proxy对真实对象进行代理,进行一些额外操作(e.g.:增强行为、负载均衡等)
3.单例模式,有五种写法,可以参考文章单例模式的五种实现方式
- 普通单例
- lazyloading+syn单例
- lazyloading+doublecheck单例
- 枚举
- 最后一种不知道,查了发现是静态内部类单例,利用静态内部类第一次访问才加载的机制实现lazyloading
4.可以考Spring中使用了哪些设计模式
分布式相关
1.分布式事务的控制。
2.分布式锁如何设计。
3.分布式session如何设计。
4.dubbo的组件有哪些,各有什么作用。
duboo不熟悉
5.zookeeper的负载均衡算法有哪些。
zookeeper就会个zab,不过负载均衡无非是公平轮询、加权轮询、随机轮询或者维护某些资源信息的动态路由这几种
6.dubbo是如何利用接口就可以通信的。
不太熟,估计涉及到服务注册以及序列化反序列化相关内容
缓存相关
1.redis和memcached的区别。
2.redis支持哪些数据结构。
3.redis是单线程的么,所有的工作都是单线程么。
4.redis如何存储一个String的。
5.redis的部署方式,主从,集群。
6.redis的哨兵模式,一个key值如何在redis集群中找到存储在哪里。
7.redis持久化策略。
框架相关
1.SpringMVC的Controller是如何将参数和前端传来的数据一一对应的。
2.Mybatis如何找到指定的Mapper的,如何完成查询的。
3.Quartz是如何完成定时任务的。
4.自定义注解的实现。
5.Spring使用了哪些设计模式。
6.Spring的IOC有什么优势。
7.Spring如何维护它拥有的bean。
一些较新的东西
1.JDK8的新特性,流的概念及优势,为什么有这种优势。
2.区块链了解
3.如何设计双11交易总额面板,要做到高并发高可用。
最后
希望我总结的这些东西对你们会有帮助,你们看完之后有什么的不懂的欢迎在下方留言讨论,也可以私信问我,我一般看完之后都会回的,也可以关注我的公众号:前程有光,马上金九银十跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料,文章都会在里面更新,整理的资料也会放在里面。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。