interview

Java 基础

  • 为什么需要装插箱?
    • 为了兼容集合类,由于 List 不放便,因此使用装箱类型表示他的集合 List
  • Method.invoke(obj,args) / Method.invoke(null,args) static / non-static 字段同理。
  • 异常中 finally 中的字节码实现,是将 Finally 块中的代码分别复制到 try 和 catch 末尾的。

IO

  • 基本的 IO 模型:阻塞、基于流的。
  • 基本的 IO 模型:由于是阻塞的,因此被迫有多个线程才能同时处理多个 IO 事件、有多个线程就会产生竞争条件(锁、同步、CAS)
  • 为什么需要 NIO:线程、上下文切换及其昂贵、IO 多路复用。

JVM

  • 栈内存分配?何时分配?
    • 当代码中创建了「无人引用」的对象时,编译器在编译过程会将代码优化,当方法栈被调用时该对象在栈上进行分配,简轻 GC 的负担。
  • 方法区常量池
    • Class 对象,Java 7 永久代、Java 8 元空间。
  • 字节码转换:编译器前端后端
    • java -> 抽象语法树 -> class (编译器前端)
  • 字节码指令(参照 Java 虚拟机规范)
    • INVOKESPECIAL:调用特殊的方法:construct、private、super、static 方法
    • INVOKEVIRTUAL:方法调用
    • PUTFIELD:对象写值,例如:user.name = “xxx”;
  • Java 8 的三层类加载体系:
    • Bootstrap (JVM 核心类) / Ext(扩展类加载器) / App (应用类加载器)
  • 常用 JVM 命令与问题排查
    • jps:列举 jvm 进程信息。jstack:线程调用栈信息。jmap:dump 堆内存信息。
  • Java 的引用链
    • 日常使用的都是「强引用」,弱引用、虚引用、影子引用
  • 什么是垃圾?
    • 可达性分析,沿着 GC root 能访问到的都是非垃圾、其他都是垃圾
  • GC root 有哪些?
    • 线程、native 方法、栈帧中的局部变量表、Class 引用的 static field
  • 什么是 safepoint?
    • 所有线程停止的地方(安全点)
  • GC  过程
    • 等待 safepoint (干活的人都停下来)
    • 引用分析 (找到垃圾)
    • Finalize 队列 (把垃圾放到队列,最终判断是否要回收)
    • 分代垃圾回收 (回收垃圾)
  • 分代垃圾回收
    • 年轻代:Survivor0 / Survivor1 / Eden
    • GC 次数多了,提升到老年代。
    • 永久代 / 元空间
  • 不同的垃圾回收算法
    • 老年代/年轻代(串行、并行算法),年轻代:压缩拷贝 老年代:压缩整理
    • 之前的 GC 需要一个 STW 过程,因此为了降低 STW 的时间使用了 CMS,又由于与之前的串行并行不兼容,因此单独使用了一个 ParNew
    • 当前算法:G1。 未来算法:Shenandoad / ZGC
  • GC 调优:参考网上文档,看看过程。
  • GC 问题:OOM、CPU 100% ( cpu 一值在执行,死循环 )

多线程基础

  • Thread / Runnable / Callable 关系
    • Thread 是真正的线程,对应内核中的线程。Runable 无法返回,且不抛出 checked 异常。Callable 只能交由线程池运行。
  • Thread 声明周期:
    • new: 线程创建
    • runnable: 分配了任务
    • terminated: run 方法执行完。
    • blocked: 线程阻塞、等待获取锁
    • waiting:  等待、等待被唤醒
    • time waiting: 超时等待。
  • ThreadLocal 实现原理:
    • 具体的数据并非保存在 ThreadLocal,而是保存在线程里面。好处就是:线程结束或者销毁,他所持有的数据也会被销毁。
  • InterruptedException 异常,当 wait、sleep 等方法运行抛出。用于线程任务的中断。

线程同步

  • synchronized 锁住的是什么?
    • 锁住的永远都是对象,有普通对象实例,Class 对象,this
  • synchronized 底层实现
    • 方法:modifiers - sync  有对应的标志位
    • 代码块:进入的时候有 MONITORENTER / MONITOREXIT 两条字节码指令。
  • 锁升级(膨胀)、锁消除、锁粗化、自旋锁(八股先不急)
  • volatile 是什么?
    • 关键字,保证了共享变量对其他线程的可见性,任何一个线程写入之后,其他线程应该都可见。
    • 实现:JVM 立刻把共享变量刷新到主内存,其他线程都从主内存读即可。
  • 指令重排序
    • 编译器发现同一线程的指令执行互不干扰,于是与不同的顺序方式去执行。
  • 为什么有同步的时候不需要 volatile ?(有规定)
    • synchronized / Lock / AtomicX

JUC 包(简化多线程开发)

  • 乐观锁和悲观锁
    • 悲观锁,Synchronized,锁住的对象在某一时刻只能被一个线程持有,严格保证了数据安全,但是上下文切换的性能开销相对较大。例子:一群人维圈抢一个凳子,谁快谁先做椅子,抢到椅子的人离开后,剩下那群人继续抢这个椅子,一直持续下去。
    • 乐观锁,基于 CAS。
  • AQS (抽象的队列同步器)原理:基于队列的同步容器(工具),里面有一个状态,该状态是原子更新的,如果没有办法成功更新该状态,则代表没有线程获取锁,反之有线程获取到锁。
  • CAS 实现和底层原理:
    • 原理:内存位置的内容与给定值进行比较,并且仅当它们相同时,才将该内存位置的内容修改为给定的新值。
    • 例子:内存位置 V,程序执行从 V 中取到的值为 10,需要对值进行自增 1 操作,因此新值为 11 。程序开始执行,之前从 V 中取到的值为 10,获取当前 V 中的值,如果为 10,表示 V 中的值没有被修改,因此把 11 放进 V 中,如果为 11  表示 V 中的值被其他线程篡改了,因此此次更新失败,并把现在 v 中的值 11 返回保存,继续进行重试,直到成功。
  • ConcurrentHashMap 原理:
    • Java 8:哈希表加链表或红黑树,并发扩容,CAS。锁的粒度很细。
  • CountDownLatch:主线程 await 等待,其他线程使用 countDown 数量减1,数量为 0 时,继续执行主线程代码。
    • 主线程:做核酸。其他线程:核酸排队,10人一组。工作人员清点组人数,来一个就减 1 直到减为 0,安排该组进行核酸。
  • CyclicBarrier:await 等待,当所有数量的线程都在 await,开始执行。
    • 每个大白需要负责一条核酸线路,共 5 条。需要所有大白在场,才开始进行核酸检测,先来的大白就等待(await),直到5个人都来齐了,开始进行核酸检测。
  • 线程池(一家公司,核心员工数量 预计10人,除核心员工外还可以多招5个外包员工)
    • 参数:核心员工、外包员工人数、超过多长时间没事干就开除员工
    • 如何工作:
      1. 当新项目来的时候,发现核心员工数量不到10人,招一个核心员工来处理这个项目。
      2. 如果项目来的时候,所有核心员都在干活,那我们就给尝试给项目排期(放入队列)
      3. 如果排期失败(核心员工没时间处理新项目),就去招一个外包员工。如果人数已经达到最大上限(核心员工+外包员工 = 15)或者公司倒闭了,就拒绝该新项目(拒绝项目也有策略)。
    • 公司接不了新项目了,如何拒绝:
      • 放弃该项目
      • 谁建的新项目,自己去执行
      • 直接丢弃排期项目中最晚的项目,然后再把该新项目加入排期,再去试试看能不能完成。
      • 直接摆烂、不管这个丢弃的项目
    • 需要注意的地方
      • 需要明确项目中最大支持的线程
      • 大概知道线程池的工作状态

计算机原理/操作系统

  • 进程之间的通信:管道 / 信号 / 共享内存 / socket (tcp)
  • 僵尸进程
    • 当父进程创建了子进程,且子进程销毁后,子进程会留下 task 数据结构,exit code 等相关信息保留一段时间,便于父进程调用,此时子进程是僵尸状态。父进程调用后就会删除相关信息,但是当父进程不调用或者自身销毁后,子进程的留下的信息如何处理?
    • 解决方法:Linux 的 init 进程会定时把自身设置僵尸进程的父亲,然后把他们清理。
  • 常见系统问题排查与僵尸进程

TCP / UDP / HTTP / HTTPS / SSH

  • tcp 可靠传输,自动重传。三次握手、四次挥手
    • 明文、基于流。
  • udp 尽最大可能交付,允许错误发生。场景:视频、语音、直播。
  • SSH 连接流程
    • 在服务器上设置自己的公钥信息。
    • 使用 SSH 命令连接服务器,此时 SSH 会用你的私钥加密一小段数据,传输给服务器。
    • 服务器使用公钥,进行解密,解密成功建立连接。
  • RSA 非对称加密
    • 公加私解:保密传输
    • 私加公解:数字签名
  • HTTPS 的 SSL handshake 流程
    • 请求服务器,服务器用私钥加密一段数据(数字签名),把加密后的数据和公钥返回给客户端
    • 计算机中的 CA 机构对公钥进行检查认证,验证通过往下执行,否则会提示。
    • 客户端根据用返回的公钥对加密的数据进行解密,解密成功即可确认该服务器是想要请求的服务器。
    • 客户端用公钥加密一段数据(对称加密算法),返回给服务器。服务器用私钥进行解密,得到对称加密算法,此时客户端与服务器 HTTPS 连接已经建立完成
    • 后续的通信使用对称加密算法,对内容进行加密解密。

Redis

  • Redis 为什么这么快?
    • 单线程,避免了上下文切换。
    • 数据结构优化
    • IO 多路复用
  • Redis 支持的数据结构
    • String / List / Set / Zset / Hash
    • 延时队列实现 - Zset score 设置为时间戳
    • 不带消费保证消息队列
  • Redis 的两种持久化
    • RDB / AOF:全量 / 增量
    • BGSAVE / CopyOnWrite
  • Redis 淘汰策略
    • LRU - Least Recent Used 最近最少使用
    • noeviction 不淘汰,内存超过设置的值丢出异常。
    • allkeys-lru 所有的 key 使用 lru 的方式淘汰
    • volatile-lru 过期的 key 使用 lru 的方式淘汰
    • allkeys-random 所有的 key 随机淘汰
    • volatile-random 所有过期的 key 随机淘汰
    • volatile-ttl 淘汰超时的 key,和快要过期的 key
  • Redis 主从?
    • 一个 Master, 若干个 Slave 同步
  • Redis 哨兵机制
    • Master 和若干个 slave,以及 Sentinel,其中 Sentinel 用来监控 redis 集群状态。当 master 挂掉,Sentinel 就会知道,并且选一个 slave 称为新的 master,然后通知应用程序做变更。
  • 分布式锁
    • SETNX TIMEOUT,命令在指定的 key 不存在时,为 key 设置指定的值,这种情况下等同 SET 命令。当 key存在时,什么也不做。并设置个超时时间避免死锁。
    • redlocks?
  • 缓存雪崩
    • 缓存突然挂了,所有的流量涌到数据库上
    • 解决方案:限流(熔断)只处理指定数量流量、服务降级。
  • 缓存穿透
    • 使用一个不存在的 key 频繁 访问数据库。
    • 解决方法:把找不到的情况也放在 redis 中,同正常的情况一致处理。
  • 缓存与数据库的一致性(左耳朵耗子:缓存更新的条路)

数据库

  • B+ 树,是多叉树,高度很低。Hash 索引。
  • B+ 树特点:减少磁盘 IO,每次读取尽可能多的数据、范围查找、磁盘预读(把数据和周围的数据都读取做缓存)
  • Hash特点:快,但是无法进行范围查找
  • 聚簇索引和非聚簇索引
    • InnoDB 聚簇索引,只有主索引关联对应并持有的数据,非主索引存储主索引的指针(也称回表),因此通过非主索引的查找如下:非主索引 -> 主索引 -> 数据。
    • MyISAM 非聚簇索引,主索引和非主索引都关联并持有数据。
  • 乐观锁悲观锁
  • 各种隔离等级
  • MySQL 索引优化
    • 最左匹配原则:创建联合索引 “a,b,c” 时,实际上创建了三个索引,a索引,a,b 索引,a,b,c 索引。根据查询条件选择索引。
    • 排查:explain 命令,查看对应的语句使用索引的情况。
    • 索引失效:不满足最左匹配或者使用了函数。例如:where date(create_at) < ‘2022-05-01’
  • MySQL 主从
    • 什么是 Binlog: 记录所有对数据库更改的一个日志,二进制文件。用于数据库主从同步和备份。
    • 主从同步过程:主库发生写请求,对应的 Binlog 会进行修改。binlog 会发送给从库,binlog在从库中有个 relaylog(中继日志),把relaylog 和当期那从库合并。

消息队列

RPC 与 Dubbo

  • RPC 是如何工作?
    • 代理模式:JDK 动态代理、Cglib 字节码增强
    • 二者优缺点:本质都是生成一个字节码,JDK 只能代理接口,而 Cglib 是代理一个类,功能更强大。
  • Dubbo 是如何工作?
    • 官网图
  • 负载均衡、服务注册、健壮性
    • 负载均衡算法:roundrobin(轮流)、加权轮流、Sticky(粘性)、hash(客户端 ip 进行 hash)
    • 服务注册:每个服务提供者把自己注册到服务中心中
    • 健壮性:
      • 当注册中心挂掉,不影响到消费者和服务者通信。
      • 当服务提供者挂掉后,服务消费者应自动的重连下一个机器,自动恢复
      • 所有的提供者挂掉,消费者可以预先配置默认的返回值
  • 限流算法
    • 计数器:设定接口的值,例如1000,每个请求进来都减1,当变成负数之后,不处理请求。后台设置定时任务,每秒将该值设置为 1000,这样就可以达到 1000qps。缺点:1000 瞬间变成0,服务器会一直拒绝其他请求,导致无响应。
    • 漏桶:请求都放在漏斗中,处理完成的请求通过尖嘴流出,漏斗溢出的请求不处理。
    • 令牌桶:令牌生成器,往桶里放令牌。每个请求需要去桶拿令牌消耗,能拿到请求继续,否则请求结束。当令牌桶的令牌被消耗完之后,请求则需要等待令牌生成器生成令牌,才能往下继续。

Zookeeper 与分布式

  • 分布式序列号生成算法
    • 雪花算法
  • 分布式事务
  • 分布式一致性
    • 强一致:用户所做的修改所有服务器上立刻得到更新,可用性和性能相对差
    • 弱一致(最终一致):下完订单,刷新回到未付款状态,再次刷新已支付
  • 保证分布式一致性
    • 一致性 Hash,请求计算 hash 选择最近的 hash 节点。节点分布不均,可以设置虚拟节点

Spring / SpringMVC / Spring Boot

  • IOC / DI 及其实现原理
  • 容器 / bean 声明周期
  • AOP 实现及原理:JDK 动态代理和 Cglib 字节码增强。
  • 设计模式:refresh 模板方法,单例模式,代理模式,工厂模式。
  • Spring Boot 改进
    • 流程配置全部自动化掉,Spring 和 SpringMVC 有的他都有
    • 基于注解的,减少 xml
    • Auto configuration 自动配置
    • 内置 Tomcat 等 Servlet 容器
  • Spring Boot 缺点
    • 像魔法,封装的太好了。使用者不明白他是如何工作的
    • jar 包比较大,内存比较多

CI 与持续集成

  • 概念:程序员写代码,提交代码,测试环境中进行测试,测试无误发布到生产环境,生产环境出 bug 又要回滚。可以看到从提交代码之后的每个步骤,程序员都需要手动的进行操作,比如发布,部署项目,手动测试项目,回滚项目等等。我们要做的是,程序员只需要负责编写和提交代码,剩下的工作都交给自动化工具去做。
  • 流程:本地开发环境 -> 版本控制系统 (Git) -> CI 服务器(Jenkins)-> 测试环境服务器、预生产服务器、生产服务器

what do you do?

  • 研究文本抄袭算法 SimHash、了解 Handlp、斯坦福 MOSS 服务。
  • 学英语
  • Java 8 - Java 18 历代的变化,有哪些新特性,自己的项目在使用 Java 17
  • 简单学习了 LaTeX,CV 也是使用开源项目 Awsome CV 和 LaTex 编排的。
  • 学习 Groovy 脚本语言

interview
http://wszzf.top/2022/04/28/interview/
作者
Greek
发布于
2022年4月28日
许可协议