面试题

216 阅读14分钟

Java 篇

1. synchronized 和 lock 的区别

  • synchronized属于jvm层面(底层是依赖 monitor 对象完成的 monitorenter 和 monitorexist),是java的关键字,是内置特性;而Lock属于api层面,是java5后产生的一个接口
  • synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  • synchronized既可以加在方法上,也可以加载特定的代码块上,括号中表示需要锁的对象。而Lock需要显示地指定起始位置和终止位置。synchronzied是托管给jvm执行的,Lock锁定是通过代码实现的。
  • ,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  • Lock可以让等待锁的线程响应中断(tryLock(Long timeout,TimeUnit unit)),而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。Lock可以提高多个线程进行读操作的效率。
  • synchronized底层用monitor对象完成 (monitorenter、monitorexit)
  • synchronized是非公平锁;ReentrantLock可以是公平锁,也可以是非公平锁,默认是非公平锁,通过构造方法传入boolean值来确定。
  • Condition: ReentrantLock用以实现分组唤醒需要唤醒的线程,可以精确唤醒,而不像synchronized要么随机唤醒一个,要么全部唤醒。

2. CAS

CAS 全称“CompareAndSwap”,即比较并替换

定义:CAS 操作包含三个操作数----内存位置(V)、期望值(A)和新值(B)。如果内存位置的值与期望值匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。

原理:CAS 是 Java 借助“C 语言” 来调用 cpu 底层指令实现的。以常用的Intel x86平台来说,最终映射到的cpu的指令为“cmpxchg”,这是一个原子指令,cpu执行此命令时,实现比较并替换的操作!系统底层进行CAS操作的时候,会判断当前系统是否为多核心系统,如果是就给“总线”加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说CAS的原子性是平台级别的!

问题:ABA问题。CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新, 但是如果一个值原来是A,在CAS方法执行之前,被其它线程修改为了B、然后又修改 回了A,那么CAS方法执行检查的时候会发现它的值没有发生变化,但是实际却变化了。 这就是CAS的ABA问题。

ABA解决方法:

  • 最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它的版本号,CAS操作时都去对比此版本号。
  • AtomicStampedReference 主要包含一个对象引用及一个可以自动更新的整数“stamp”的pair对象来解决ABA问题。(这周就会出一篇关于 AtomicStampedReference 的文章)

Mysql 篇

1. MySQL 有几种日志 (20210324)

  1. 重做日志(redo log)
  2. 回滚日志(undo log) undolog 回滚日志,保证了事务的原子性。提供回滚和多个行版本控制(MVCC)。

Undo Log 也是逻辑日志,如何理解逻辑日志呢。可以这么想:每当我们新增一条数据对应逻辑日志里就删除一条;反过来也一样;更新一条数据就反向更新一条(+10 就记录 -10)。那么我们就详细说一下增删改。

insert 操作无需分析,就是插入一行数据而已。 , elete 操作实际上不会直接删除,而是将 delete 对象打上删除标记,当事务结束后就会删除。

update 分为两种情况:update 的列是否是主键列

如果不是主键列,在 undo log 中直接反向记录如何 update 的。即 update 是直接进行的。 如果是主键列,update 分两部分执行:先删除该行,再插入一行目标行。

  1. 二进制日志(binlog)

  2. 错误日志(errorlog)

  3. 慢查询日志(slow query log)

    当语句执行时间较长时,通过日志的方式进行记录,这种方式就是慢查询的日志。

    通过 set global slow_query_log = on; 开启慢查询日志

    那么执行多久算慢呢,我们通过 set long_query_time = n; n 表示秒,如果语句执行时间超过 n 秒,那么就算慢查询。具体数值根据业务商定。

    我们通过查看慢查询日志可以发现,很乱,数据量大的时候,可能一天会产生几个G的日志,根本没有办法去清晰明了的分析。所以,这里,我们采用工具 mysqldumpslow 进行分析。

    mysqldumpslow -s c -t 10 /data/mysql/slow.log

    -s 以执行次数进行排序,-t 前10 条。这样我们就可以通过执行次数等条件对慢 sql 进行优化。

  4. 一般查询日志(general log)

  5. 中继日志(relay log)

explain

type

  • ALL:全表扫描 explain select * from student;
    生产中几乎没有这种需求。
  • INDEX:全索引扫描 explain select id from city;
    查询某个索引列的所有值,生产中很少使用。
  • RANGE:索引范围扫描 select 范围查询:
    例如:>、<、>= 、<=
    in or
    between and
    explain select * from city where id < 10;
  • REF: 辅助索引的等值索引查询。
    select * from city where countrycode = 'CHN'
    union all
    select * from city where countrycode = 'USA'
    REF 的效率要比 RANGE 高
  • system 或 const:(他们俩一样) 主键或唯一键,等值查询。 假设:id 为城市表 city 的主键,那么通过主键查询效率最高

image.png

  • NULL: 索引中不包含查询的值(也就是没查着)。 比如表里一共有 100 条数据,我们查第 一万条

image.png 经过分析,我们对 type 有所了解,我们希望我们的语句至少在 RANGE 以上。

Extra

  • Using where        SQL 使用了 where 条件过滤数据。
           并不代表不需要优化,往往需要配合结果中的 type 来综合判断。常见的手段为在 where 过滤条件上增加索。
  • Using index        SQL 所需要返回的所有列数据均在一棵索引树上,而无需访问实际的行记录。这类 SQL 语句往往性能较好。
  • Using index condition        确实命中了索引,但不是所有的列数据都在索引树上,还需要访问实际的行记录。这类SQL语句性能也较高,但不如Using index。 (假如,id 和 name 为主键和索引列,而 sex 为普通列)

image.png

  • Using filesort        得到所需结果集,需要对所有记录进行文件排序。文件排序需要消耗 cpu,所以这类 SQL 性能极差,需要优化。在一个没有建立索引的列上进行了order by,就会触发filesort,常见的优化方案是,在 order by 的列上添加索引,避免每次查询都全量排序。如果 order by 的列上加了索引后还没有效果,可尝试给索引列和 order by 的列组成联合索引。

image.png

  • Using join buffer (Block Nested Loop)        这类SQL语句性能往往也较低,需要进行优化。两个关联表join,关联字段均未建立索引,就会出现这种情况。常见的优化方案是,在关联字段上添加索引,避免每次嵌套循环计算。

JVM

1. 类的加载过程(20210324)

加载、验证、准备、解析和初始化

  • 加载
  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 class 对象,作为方法区这个类的各种数据的访问入口
  • 验证 验证是连接阶段的第一步,这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信息被当代代码运行后不会危害虚拟机自身的安全。
  • 准备 为类变量分配内存并设置初始值,不会为实例变量分配,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。
  • 解析 解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程
  • 初始化 执行类构造方法(完成静态属性和静态代码块变量的赋值操作)的过程,此方法不需要定义,是 javac 完成的。

2. 判断对象是否存活(20210324)

  • 引用计数法        给对象中添加一个引用计数器,每当有一个地方引用它,计数器值就加 1;当引用失效时,计数器值就减 1;任何时刻计数器为 0 的对象就是不可能再利用的。该方法实现简单,判定效率高,但很难解决对象之间相互引用的问题。
  • 可达性分析算法        通过一些列称之为“GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到 GC Root 没有任何引用链相连,则证明此对象是不可用的。
    在 Java 中,可作为 GC Root 的对象包括以下几种:
           ① 虚拟机栈中引用的对象
           ② 方法区中类静态属性引用的对象
           ③ 方法区中常量引用的对象
           ④ 本地方法栈中 JNI(Native 方法)引用的对象

3. 回收算法

标记 - 清除算法(Mark - Sweep)

算法分为“标记” 和 “清除” 两个阶段:首先标记出所有被引用的对象,也就是活着的对象,可达的对象。第二阶段对所有对象进行遍历,如果发现某个对象没有标记为可达对象,则将其回收。
缺点:① 效率不算高(标记和清除都需要遍历)
           ② 这种方式清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表。
           ③ 在进行 GC 的时候,需要停止整个应用程序,导致用户体验差。

空间碎片可能会导致当以后程序在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

何为清除? 所谓清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置是否够,如果够,就存放。 白话:所谓清除,不没有消失,当下次有对象分配进来直接覆盖。

复制算法

背景:为了解决标记-清除算法那在垃圾收集效率方面的缺陷。
核心思想:
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清理正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
优点:

  • 没有标记和清除过程,实现简单,运行高效
  • 复制过去以后保证空间的连续性,不会出现“碎片”问题
    缺点:
  • 就是需要两倍的内存空间。
  • 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销也不下小。

特别的:
如果系统中的垃圾对象很多,复制算法不会很理想。因为复制算法需要复制的存活对象数量并不会太>大,或者说非常低才行。(如果存活的较多,那么我们需要复制的对象就非常多,需要维护对象引用关系>就很多。)

针对这个性质,我们会发现新生代的对象都是朝生夕死的,通常可以回收 70%~99% 的内存空间,回收性价比很高。也就是说存活的对象很少,所以很适合复制算法。(即s0,s1 区)

4. 双亲委派

Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交给由父类处理,它是一种任务委派模式。

工作原理: 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;

如果如类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;

如果父类加载器可以完成加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子类加载器才会尝试自己去加载,这就是双亲委派模式。

网络

1. HTTP 与 HTTPS 的区别(20210329)

  • 端口:HTTP 的 URL 由 “http://” 其实且默认使用端口 80,而 HTTPS 的 URL 由“https://” 起始且默认使用端口 443。
  • 安全性和资源消耗: HTTP 协议运行在 TCP 之上,所有传输内容都是明文,客户端和服务器端都是无法验证对方身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都是经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP耗费更多服务器资源。

2. TCP 和 UDP 的区别(20210329)

  • UDP 在传输数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如:QQ 语音、QQ 视频、直播等等。
  • TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制,在数据传完后,还会断开连接来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景 。