哈喽。我又来为大家更新面经资料了。今天继续为大家更新字节跳动社招的面经资料。
(我发现我特别喜欢整理字节的。。。)
这篇文章主要是分享给有面试需求的朋友们。我整理了多家互联网大厂的多年面试题,多位大佬的面经资料,以及Java学习资料。有需要的朋友可以点击进入获取。暗号:CSDN。
面试题
面试时长2H。应用+原理理解;Java后端社招+3年经验
1、自我介绍
2、JVM组成部分
(1)堆
(2)方法区(元数据区)
(3)程序计数器
(4)本地方法栈
(5)虚拟机栈
3、虚拟机栈和本地方法栈区别(线程私有,每个线程都有一个)
(1)本地方法栈和虚拟机栈的作用相同,用来描述方法执行的内存模型;
(2)即每个方法执行都会在栈上创建一个栈帧,存放方法的出入口,局部变量表和操作数等信息.当一个方法开始与执行完毕对应的栈帧就会入栈与出栈;
4、可以用堆代替栈嘛(区别)
(1)栈是描述该线程当前执行中方法的内存模型,堆存放程序运行时创建的对象;
(2)堆生命周期是和程序一致,线程共享,栈生命周期和执行线程一致,且只用于当前线程,所以内存大小比堆小得多;
(3)栈内存地址要求连续,统一分配好.并且栈后入先出,方法执行完毕栈帧弹出内存及时回收。保证栈的读取速度;
(4)堆内存地址不一定连续,并且内存回收由GC回收非实时回收;
(5)栈内存放入堆中由于大部分线程执行周期短会造成大量垃圾对象或者长期对象也会有内存泄露的问题,一直跟随垃圾回收进行迁移;
(6)所以堆和栈的内存管理方式不一样,用堆代替栈会增加堆内存回收的复杂性,分开更容易管理各自内存
(7)两者内存作用不同,栈中溢出会提示栈溢出错误,堆中溢出会提示内存溢出错误,如果放在一起则无法明确定位错误
分开的意义在于两个内存释放时机要求不同导致的内存管理方式不一样,如果放到一起会增加现有垃圾回收复杂度.并且堆栈内存的存取速率以及隔离性要求不一致,使用堆来存放也会增加堆内存管理复杂度以及降低线程执行效率。
5、JVM为啥要有垃圾回收器
JAVA语言未提供手动释放对象资源的方式,内存空间释放由JVM虚拟机自己判断与执行,所以提供垃圾回收器帮我们进行内存资源释放.这样也避免了我们申请完内存后如果忘了释放内存而造成的内存泄漏。
6、 如何定位垃圾,哪些是ROOT节点
(1)类常量和静态属性引用的对象(static,final);
(2)虚拟机栈和本地方法栈中引用的对象(局部变量表);
7、Java程序占用CPU100%如何排查解决
前言:CPU占用100则表示当前有计算密集类型线程正在执行并处于无法退出/执行计算难度很大的状态(比如死循环和大量对象未死亡时的GC扫描阶段)
(1)查看GC是否正常,当有大量小对象存在,则GC会非常占用资源;
(2)查看占用资源的线程信息,根据线程执行信息定位代码问题;
(3)查看占用最高进程: top查看占用最高PID;
(4)查看该进程的可疑线程: top -H -p PID 查看当前进程中占用最高线程;
(5)打印输出可疑线程16位字符: printf “%x\n” 线程PID或echo “obase=16;线程PID” | bc;
(6)打印程序线程并查询对应线程信息: jstack pid | grep -A num 16进制PID
(7)查看线程栈信息的调用路径以及当前所处方法,查看代码逻辑;
8、阻塞状态线程会让CPU100嘛
CPU使用率的飙升更多是由于执行计算密集型任务导致的,而处于阻塞状态的线程在等待时间分片尚未执行,所以不会导致CPU使用率上升
9、死锁为啥会让CPU占用100
(1)当出现锁竞争时,自旋锁会一直循环执行获取锁操作直至到达重试次数或者获取到锁,使得CPU陷入忙等待状态;
(2)适用于业务执行时间比较短的操作;
while(! lock.lock()){
//...
}
10、Connection=keep-alive干嘛的
(1)HTTP每次请求都会执行TCP连接握手操作,比较消耗资源而且性能也不是很好,所以通过客户端告知服务端建立长连接方式,进行长连接复用.客户端的下次请求即可使用该长连接进行发送;
(2)HTTP1.0协议要求客户端增加Connection=keep-alive表明长连接请求,服务端也需要返回加Connection=keep-alive表示支持,然后双方建立长连接,客户端可复用该条连接请求;
(3)http1.1默认使用加Connection=keep-alive的长连接模式,默认的连接时长可以在服务器端设置不会让它长时间连接防止无效资源占用;
(4)长连接可以保证客户端可进行连接复用,但不能使服务器端主动向客户端发送请求(复用的是底层TCP的Socket连接,但需要和TCP的keep-alive区分开)
11、 为啥用Websocket做实时通信连接, JSONP了解吗.
(1)Websocket是基于TCP的全双工通信,可以做到客户端和服务器端实时发送信息,最主要解决服务器端主动向客户端推送信息的问题
(2)一个客户端建立一条websocket连接即可,不需要创建大量HTTP连接请求,减少服务端压力
(3)减少大量重复请求头的传输,减少网络带宽压力
(4)JSONP
12、长轮询机制不能做通信吗
(1)长轮询对于不同的实时信息传递需要有不同的请求连接,并且需要服务端HOLD住请求,会占用大量服务资源;而websocket可以将一条tcp通道复用来传递多种类型信息,传输信息再通过文本协议区分;
(2)Http请求的请求头每次都需要带很多信息,占用很多网络资源
(3)无法解决服务器端主动发送消息的问题
(4)短/长轮询和WebSocket的优缺点:
- 短轮询
定义:其实就是普通的轮询。指在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。
应用场景:传统的web通信模式。后台处理数据,需要一定时间,前端想要知道后端的处理结果,就要不定时的向后端发出请求以获得最新情况。
优点:前后端程序编写比较容易。
缺点:请求中有大半是无用,难于维护,浪费带宽和服务器资源;响应的结果没有顺序(因为是异步请求,当发送的请求没有返回结果的时候,后面的请求又被发送。而此时如果后面的请求比前面的请 求要先返回结果,那么当前面的请求返回结果数据时已经是过时无效的数据了)。
实例:适于小型应用。- 长轮询
定义:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。- WebSocket
定义:Websocket是基于HTTP协议的,在和服务端建立了链接后,服务端有数据有了变化后会主动推送给前端。
优点:请求响应快,不浪费资源。(传统的http请求,其并发能力都是依赖同时发起多个TCP连接访问服务器实现的(因此并发数受限于浏览器允许的并发连接数),而websocket则允许我们在一条ws连接上同时并发多个请求,即在A请求发出后A响应还未到达,就可以继续发出B请求。由于TCP的慢启动特性(新连接速度上来是需要时间的),以及连接本身的握手损耗,都使得websocket协议的这一特性有很大的效率提升;http协议的头部太大,且每个请求携带的几百上千字节的头部大部分是重复的,websocket则因为复用长连接而没有这一问题。)
缺点:主流浏览器支持的Web Socket版本不一致;服务端没有标准的API,下图是各大主流对websocket的兼容性列表。
实例:实现即时通讯:如股票交易行情分析、聊天室、在线游戏等,替代轮询和长轮询
解决:解决了http协议的两个问题:
①服务端的被动性。http协议是只有客户端询问之后才回复。解决了同步有延迟的问题
②解决了服务器上消耗资源的问题
13、Kafka顺序消费如何保证
(1)一个队列只创建一个分区保证顺序消费
(2)一个队列上的消息投递利用key的hash结果,保证部分有序
14、Kafka中Partion和Consumer对应关系
(1)一个Partion只能被一个消费者分组中的一个消费者消费
(2)一个消费者可以消费队列中多个partion
15、Redis分布式锁先SETNX后,未来得及设置过期时间宕机了怎么办
(1)set key value nx ex seconds(原子操作)
(2)LUA脚本保证原子操作
16、Redis内存不足时怎么样
(1)执行淘汰策略.默认是返回错误;还有随即淘汰和最近最少使用淘汰策略(注意设置maxmemory,否则64位机器会用尽机器内存)
(2)使用集群模式,将数据分片存储(中间件解决分片/业务keyhash实现)
(3)减少大key产生,查询大key优化
17、Redis如何查看大KEY
(1)redis-cli --bigkeys 查看大Key;基于scan命令,不用担心阻塞问题.对String类型统计字节长度,对集合类型统计元素个数.
(2)debug object key 查看对应key的序列化后长度
(3)手动执行bgsave命令,生成rdb文件,通过redis rdb tools工具分析rdb文件
(4)memory usage key 查看对应key的内存使用(4.0+版本)
18、 LUA脚本有了解吗
19、 Redis如果有大量Key同一时间失效怎么办
我们使用缓存的主要目是提升查询速度和保护数据库等稀缺资源不被占满。而缓存最常见的问题是缓存穿透、击穿和雪崩,在高并发下这三种情况都会有大量请求落到数据库,导致数据库资源占满,引起数据库故障。
(1)概念
- 缓存穿透
在高并发下,查询一个不存在的值时,缓存不会被命中,导致大量请求直接落到数据库上,如活动系统里面查询一个不存在的活动。- 缓存击穿
在高并发下,对一个特定的值进行查询,但是这个时候缓存正好过期了,缓存没有命中,导致大量请求直接落到数据库上,如活动系统里面查询活动信息,但是在活动进行过程中活动缓存突然过期了。- 缓存雪崩
在高并发下,大量的缓存key在同一时间失效,导致大量的请求落到数据库上,如活动系统里面同时进行着非常多的活动,但是在某个时间点所有的活动缓存全部过期。
(2)常见解决方案- 直接缓存NULL值
- 限流
- 缓存预热
- 分级缓存
- 缓存永远不过期
(3)layering-cache实践
在layering-cache里面结合了缓存NULL值,缓存预热,限流、分级缓存和间接的实现"永不过期"等几种方案来应对缓存穿透、击穿和雪崩问题。- 直接缓存NULL值
应对缓存穿透最有效的方法是直接缓存NULL值,但是缓存NULL的时间不能太长,否则NULL数据长时间得不到更新,也不能太短,否则达不到防止缓存击穿的效果。
我们在layering-cache对NULL值进行了特殊处理,一级缓存不允许存NULL值,二级缓存可以配置缓存是否允许存NULL值,如果配置可以允许存NULL值,框架还支持配置缓存非空值和NULL值之间的过期时间倍率,这使得我们能精准的控制每一个缓存的NULL值过期时间,控制粒度非常细。当NULL缓存过期我还可以使用限流,缓存预热等手段来防止穿透。
示例:
@Cacheable(value = "people", key = "#person.id", depict = "用户信息缓存",
firstCache = @FirstCache(expireTime = 10, timeUnit = TimeUnit.MINUTES),
secondaryCache = @SecondaryCache(expireTime = 10, timeUnit = TimeUnit.HOURS,
isAllowNullValue = true, magnification = 10))
public Person findOne(Person person) {
Person p = personRepository.findOne(Example.of(person));
logger.info("为id、key为:" + p.getId() + "数据做了缓存");
return p;
}
在这个例子里面isAllowNullValue = true表示允许换存NULL值,magnification = 10表示NULL值和非NULL值之间的时间倍率是10,也就是说当缓存值为NULL是,二级缓存的有效时间将是1个小时。
- 限流
应对缓存穿透的常用方法之一是限流,常见的限流算法有滑动窗口,令牌桶算法和漏桶算法,或者直接使用队列、加锁等,在layering-cache里面我主要使用分布式锁来做限流。
layering-cache数据读取流程:
下面是读取数据的核心代码:
private <T> T executeCacheMethod(RedisCacheKey redisCacheKey, Callable<T> valueLoader) {
Lock redisLock = new Lock(redisTemplate, redisCacheKey.getKey() + "_sync_lock");
// 同一个线程循环20次查询缓存,每次等待20毫秒,如果还是没有数据直接去执行被缓存的方法
for (int i = 0; i < RETRY_COUNT; i++) {
try {
// 先取缓存,如果有直接返回,没有再去做拿锁操作
Object result = redisTemplate.opsForValue().get(redisCacheKey.getKey());
if (result != null) {
logger.debug("redis缓存 key= {} 获取到锁后查询查询缓存命中,不需要执行被缓存的方法", redisCacheKey.getKey());
return (T) fromStoreValue(result);
}
// 获取分布式锁去后台查询数据
if (redisLock.lock()) {
T t = loaderAndPutValue(redisCacheKey, valueLoader, true)