面试题分享-附参考答案

FullGC多的原因,如何优化

原因:FullGC触发的原因是老年代满了,需要整堆回收

对象被放到老年代有原因:

  1. 大对象直接分配到老年代
  2. 对象经历了很多次GC,年龄超过15
  3. 在Survivor区中,超过某个年龄的对象占比超过50%,超过年龄的对象都转移到老年代
  4. 空间分配担保,导致老年代满了,或者需要担保的空间太大无法满足要求

优化:

  1. 增大JVM参数中堆内存的设置,看是否需要修改老年代和新生代的比例
  2. 减少不必要的对象创建,对象用完赋null值帮助回收
  3. 尽量不定义大对象
  4. 如果现在使用的是CMS,评估是否可以使用G1收集器

MQ怎么保证消息不丢失?

MQ保证消息不丢失从两个方面来看:客户端的使用和服务端的配置

客户端使用
  1. 发送消息时,使用同步发送或者异步发送+check的机制,保证客户端发送消息时成功被服务端接收了
  2. 处理消息时,先进行消息的业务处理,等业务逻辑处理完之后,再返回消息的消费ack,如果ack异常了,可以考虑对消息进行持久化处理
服务端配置
  1. 在接收到消息后,采用同步刷盘的机制来保存消息到磁盘
  2. 主从同步,保证即使遭遇系统宕机、磁盘损坏的情况下也保证消息的不丢失

MQ如何处理重复消费

  1. 看业务是否具有天然的幂等性,例如在订单类系统中,可以通过使用状态判断来看消息是否已经被处理过了
  2. 如果不具备幂等性,可以考虑使用setnx来做,当收到消息之后,获取消息的唯一标示,通过唯一标示来做一个分布式锁,消费结束时如果消费成功,执行ack,这个锁就放着不动;消费失败,释放锁,不执行ack

ID生成器的原理

ID生成器是为了全局生成唯一ID的

比较常见的有UUID、数据库自增主键、雪花算法

雪花算法的结构:时间戳 + 机器ID + 序列号

缓存雪崩、缓存击穿、缓存穿透

缓存雪崩

大量缓存在同一时间失效,导致大量请求打到MySQL,或者Redis服务不可用了,导致请求直接访问到MySQL

解决方案:

  1. 失效时间的设置尽量在原定时间上加上一个随机数,避免大量key同时失效
  2. 搭建Redis集群,提高可用性
缓存击穿

缓存击穿主要是因为某个热点key在Redis突然消失了,请求全都打到了MySQL

解决方案:

  1. 热点key不设置失效时间
  2. 避免误删操作(代码、人工)
  3. 布隆过滤器
缓存穿透

连续请求某一个不存在key,导致请求直接打到数据库

解决方案:

  1. 如果发现持续访问一个不存在的key,可以在Redis中放一个默认值,或者执行降级
  2. 在前端或者web层就校验key的合法性,避免不合法的key放进来

MySQL分库分表

分库分表的策略采用主键id和表个数取模的策略

如果数据量持续增加,可以考虑增加分表的张数,分表数量 * 2,减少数据迁移工作

或者最开始就使用一致性Hash

还可以考虑范围分片的方法,并对历史数据执行归档

CPU 100% 的问题

简要步骤:

  1. 找到最耗CPU的进程
  2. 找到最耗CPU的线程
  3. 查看堆栈,定位线程在干什么,定位代码
步骤一、找到进程
  • 执行top -c,显示进程运行信息列表
  • 键入P(大写),进程按照CPU使用率排序
  • 找出最耗CPU的进程id,例如10778
步骤二、找到线程
  • 执行top -Hp 10778,显示一个进程的线程运行信息列表
  • 键入P(大写),线程按照CPU使用率排序
  • 找出最耗CPU的线程id,例如10084
步骤三、看看线程在干什么
  • 首先将线程PID转化为16进制,使用printf “%x\n” 10084,得到对应16进制为: 2764
  • 通过jstack查看堆栈信息,jstack 10778 | grep ‘0x2764’ -C5 —color,得到线程堆栈
  • 找到线程对应的线程名称,找到对应代码

线程池的参数评估

首先要明确任务的类型

如果是CPU密集型任务,那么核心线程数设置为:CPU核数+1

如果是IO密集性任务,那么就需要大致估算出IO时间和CPU时间的比例,最佳线程数就是

[(IO耗时/CPU耗时)+1]*CPU核数

上面说的是核心线程数,那么最大线程数则可以比核心线程数稍微多一些

然后就是阻塞队列了,阻塞队列不宜使用LinkedBlockingQueue(其实LinkedBlockingQueue也是有界的,为Integer的最大值,并且也可以设置容量),而应该使用有界的ArrayBlockingQueue,这个队列大小也需要根据访问量来估计,减去核心线程数在处理的任务,得出一个数值