Skip to content

Commit

Permalink
京东面经
Browse files Browse the repository at this point in the history
  • Loading branch information
itwanger committed Jun 29, 2024
1 parent bb9adcd commit dee7ba6
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 50 deletions.
6 changes: 3 additions & 3 deletions docs/sidebar/sanfene/collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -908,9 +908,9 @@ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
### 21.那扩容机制了解吗?

扩容时,HashMap 会创建一个新的数组,其容量是原数组容量的两倍。
扩容时,HashMap 会创建一个新的数组,其容量是原数组容量的两倍。然后将键值对放到新计算出的索引位置上。一部分索引不变,另一部分索引为“原索引+旧容量”。

然后将键值对放到新计算出的索引位置上。一部分索引不变,另一部分索引为“原索引+旧容量”
>为了便于理解,我会结合 JDK7 和 JDK8 两个版本来讲
在 JDK 7 中,定位元素位置的代码是这样的:

Expand Down Expand Up @@ -1018,7 +1018,7 @@ final int hash(Object k) {

![三分恶面渣逆袭:扩容位置变化](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/collection-26.png)

也就是说,在 JDK 8 的新 hash 算法下,数组扩容后的索引位置,要么就是原来的索引位置,要么就是“原索引+原来的容量”,遵循一定的规律。
换句话说,在 JDK 8 的新 hash 算法下,数组扩容后的索引位置,要么就是原来的索引位置,要么就是“原索引+原来的容量”,遵循一定的规律。

![三分恶面渣逆袭:扩容节点迁移示意图](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/collection-27.png)

Expand Down
14 changes: 4 additions & 10 deletions docs/sidebar/sanfene/javase.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https

面向对象编程有三大特性:封装、继承、多态。

![二哥的 Java 进阶之路](https://cdn.tobebetterjavaer.com/stutymore/javase-20240330115129.png)
![二哥的 Java 进阶之路:封装继承多态](https://cdn.tobebetterjavaer.com/stutymore/javase-20240330115129.png)

#### 封装是什么?

Expand Down Expand Up @@ -455,16 +455,10 @@ Student 类继承了 Person 类的属性(name、age)和方法(eat),同

#### 什么是多态?

推荐阅读:[深入理解 Java 三大特性:封装、继承和多态](https://javabetter.cn/oo/encapsulation-inheritance-polymorphism.html)

多态允许不同类的对象对同一消息做出响应,但表现出不同的行为(即方法的多样性)。

在我的印象里,西游记里的那段孙悟空和二郎神的精彩对战就能很好的解释“多态”这个词:一个孙悟空,能七十二变;一个二郎神,也能七十二变;他们都可以变成不同的形态,只需要喊一声“变”。

多态其实是一种能力——同一个行为具有不同的表现形式;换句话说就是,执行一段代码,Java 在运行时能根据对象类型的不同产生不同的结果。

和孙悟空和二郎神都只需要喊一声“变”,然后就变了,并且每次变得还不一样;一个道理。

多态的前置条件有三个:

- 子类继承父类
Expand Down Expand Up @@ -1279,17 +1273,17 @@ System.out.println(c.equals(d)); // 输出true

#### 什么是 Integer 缓存?

根据实践发现,大部分的数据操作都集中在值比较小的范围,因此 Integer 搞了个缓存池,默认范围是 -128 到 127。
就拿 Integer 的缓存吃来说吧。根据实践发现,大部分的数据操作都集中在值比较小的范围,因此 Integer 搞了个缓存池,默认范围是 -128 到 127。

![](https://cdn.tobebetterjavaer.com/stutymore/javase-20240323080956.png)
![二哥的 Java 进阶之路:integer 源码](https://cdn.tobebetterjavaer.com/stutymore/javase-20240323080956.png)

当我们使用自动装箱来创建这个范围内的 Integer 对象时,Java 会直接从缓存中返回一个已存在的对象,而不是每次都创建一个新的对象。这意味着,对于这个值范围内的所有 Integer 对象,它们实际上是引用相同的对象实例。

Integer 缓存的主要目的是优化性能和内存使用。对于小整数的频繁操作,使用缓存可以显著减少对象创建的数量。

可以在运行的时候添加 `-Djava.lang.Integer.IntegerCache.high=1000` 来调整缓存池的最大值。

![](https://cdn.tobebetterjavaer.com/stutymore/javase-20240323082802.png)
![二哥的 Java 进阶之路:调整缓存池大小](https://cdn.tobebetterjavaer.com/stutymore/javase-20240323082802.png)

引用是 Integer 类型,= 右侧是 int 基本类型时,会进行自动装箱,调用的其实是 `Integer.valueOf()`方法,它会调用 IntegerCache。

Expand Down
23 changes: 15 additions & 8 deletions docs/sidebar/sanfene/jvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,18 +490,25 @@ ThreadLocal 的弱引用导致内存泄漏也是个老生常谈的话题了,

### 13.如何判断对象仍然存活?

有两种方式,**引用计数算法(reference counting)**和可达性分析算法。
判断一个对象是否存活,也就等同于判断一个对象是否可以被回收。通常有两种方式:引用计数算法(reference counting)和可达性分析算法。

- **引用计数算法**
#### 什么是引用计数法?

引用计数器的算法是这样的:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
每个对象有一个引用计数器,记录引用它的次数。当计数器为零时,对象可以被回收

![引用计数算法](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/jvm-17.png)
![三分恶面渣逆袭:引用计数法](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/jvm-17.png)

- **可达性分析算法**
但无法解决循环引用问题。例如,两个对象互相引用,但不再被其他对象引用,它们的引用计数都不为零,因此不会被回收。

目前 Java 虚拟机的主流垃圾回收器采取的是可达性分析算法。这个算法的实质在于将一系列 GC Roots 作为初始的存活对象合集(Gc Root Set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。
![GC Root](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/jvm-18.png)
#### 什么是可达性分析算法?

通过一组名为 “GC Roots” 的根对象,进行递归扫描。那些无法从根对象到达的对象是不可达的,可以被回收;反之,是可达的,不会被回收。

![三分恶面渣逆袭:GC Root](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/jvm-18.png)

这也是 Java 垃圾回收器(如 G1、CMS 等)使用的主要算法。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的京东面经同学 7 京东到家面试原题:如何判断一个对象是否可以回收
### 14.Java 中可作为 GC Roots 的引用有哪几种?

Expand Down Expand Up @@ -762,7 +769,7 @@ Full GC 是最彻底的垃圾收集,涉及整个 Java 堆和方法区(或元

除了固定的年龄阈值,还会根据各个年龄段对象的存活大小和总空间等因素动态调整对象的晋升策略。

> 如果在 Survivor 空间中相同年龄的所有对象大小总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代。
比如说,在 Survivor 空间中相同年龄的所有对象大小总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的阿里面经同学 5 阿里妈妈 Java 后端技术一面面试原题:哪些情况下对象会进入老年代?
Expand Down
71 changes: 61 additions & 10 deletions docs/sidebar/sanfene/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -848,14 +848,10 @@ MySQL 的日志文件主要包括:

④、**二进制日志**(Binary Log):记录了所有修改数据库状态的 SQL 语句,以及每个语句的执行时间,如 INSERT、UPDATE、DELETE 等,但不包括 SELECT 和 SHOW 这类的操作。

以及两个 InnoDB 存储引擎特有的日志文件:

⑤、**重做日志**(Redo Log):记录了对于 InnoDB 表的每个写操作,不是 SQL 级别的,而是物理级别的,主要用于崩溃恢复。

⑥、**回滚日志**(Undo Log,或者叫事务日志):记录数据被修改前的值,用于事务的回滚。

支持事务回滚,可以用来实现 MVCC,即多版本并发控制。

#### 请重点说说 binlog?

推荐阅读:[带你了解 MySQL Binlog 不为人知的秘密](https://www.cnblogs.com/rickiyang/p/13841811.html)
Expand Down Expand Up @@ -2173,12 +2169,16 @@ GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https

#### 按锁粒度如何划分?

按锁粒度划分的话,MySQL 的锁有:

- 表锁:开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。
- 行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
- 页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般

#### 按兼容性如何划分?

按兼容性划分的话,MySQL 的锁有:

- 共享锁(S Lock),也叫读锁(read lock),相互不阻塞。
- 排他锁(X Lock),也叫写锁(write lock),排它锁是阻塞的,在一定时间内,只有一个请求能执行写入,并阻止其它锁读取正在写入的数据。

Expand Down Expand Up @@ -2413,7 +2413,7 @@ redo log 是一种物理日志,当执行写操作时,MySQL 会先将更改

确保在同一事务中多次读取相同记录的结果是一致的,即使其他事务对这条记录进行了修改,也不会影响到当前事务。

MySQL 默认的隔离级别,避免了“脏读”和“不可重复读”,也在很大程度上减少了“幻读”问题
可重复读是 MySQL 默认的隔离级别,避免了“脏读”和“不可重复读”,但可能会出现幻读

#### 什么是串行化?

Expand All @@ -2436,13 +2436,64 @@ redo log 是一种物理日志,当执行写操作时,MySQL 会先将更改
> 7. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的 360 面经同学 3 Java 后端技术一面面试原题:数据库隔离级别有哪些?mysql 是属于哪个隔离级别
> 8. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的联想面经同学 7 面试原题:Mysql 四个隔离级别,MVCC 实现
### 51.什么是幻读,脏读,不可重复读呢?
### 51.什么是脏读、不可重复读、幻读呢?

脏读指的是一个事务能够读取另一个事务尚未提交的数据。如果读到的数据在之后被回滚了,那么第一个事务读取到的就是无效的数据。

```sql
-- 事务 A
START TRANSACTION;
UPDATE employees SET salary = 5000 WHERE id = 1;

-- 事务 B
START TRANSACTION;
SELECT salary FROM employees WHERE id = 1; -- 读取到 salary = 5000 (脏读)
ROLLBACK;
```

不可重复读指的是在同一事务中执行相同的查询时,返回的结果集不同。这是由于在事务过程中,另一个事务修改了数据并提交。

比如说事务 A 在第一次读取某个值后,事务 B 修改了这个值并提交,事务 A 再次读取时,发现值已经改变。

```sql
-- 事务 A
START TRANSACTION;
SELECT salary FROM employees WHERE id = 1; -- 读取到 salary = 3000

-- 事务 B
START TRANSACTION;
UPDATE employees SET salary = 5000 WHERE id = 1;
COMMIT;

-- 事务 A 再次读取
SELECT salary FROM employees WHERE id = 1; -- 读取到 salary = 5000 (不可重复读)
COMMIT;
```

幻读指的是在同一事务中执行相同的查询时,返回的结果集中出现了之前没有的数据行。这是因为在事务过程中,另一个事务插入了新的数据并提交。

比如说事务 A 在第一次查询某个条件范围的数据行后,事务 B 插入了一条新数据且符合条件范围,事务 A 再次查询时,发现多了一条数据。

```sql
-- 事务 A
START TRANSACTION;
SELECT * FROM employees WHERE department = 'HR'; -- 读取到 10 条记录

-- 事务 B
START TRANSACTION;
INSERT INTO employees (id, name, department) VALUES (11, 'John Doe', 'HR');
COMMIT;

-- 事务 A 再次查询
SELECT * FROM employees WHERE department = 'HR'; -- 读取到 11 条记录 (幻读)
COMMIT;
```

可以通过设置隔离级别为可串行化来避免幻读,代价是降低并发性能。

- 事务 A、B 交替执行,事务 A 读取到事务 B 未提交的数据,这就是**脏读**
- 在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是**不可重复读**
- 事务 A 查询一个范围的结果集,另一个并发事务 B 往这个范围中插入 / 删除了数据,并静悄悄地提交,然后事务 A 再次查询相同的范围,两次读取得到的结果集不一样了,这就是**幻读**
> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的京东面经同学 7 京东到家面试原题:mysql事务隔离级别,默认隔离级别,如何避免幻读
不同的隔离级别,在并发事务下可能会发生的问题:
#### 不同的隔离级别,在并发事务下可能会发生什么问题?

| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| -------------------------- | ---- | ---------- | ---- |
Expand Down
29 changes: 10 additions & 19 deletions docs/sidebar/sanfene/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -1221,33 +1221,28 @@ public class DemoApplication {

### 16.Spring 怎么解决循环依赖的呢?

> 开发人员做好设计,别让 Bean 循环依赖,但面试官既然这么傻逼地问了这个问题,肯定不想听这个最正确的答案(😂)。只能硬着头皮作答了
Spring 通过三级缓存(Three-Level Cache)机制来解决循环依赖

我们知道,SingletonBean 要初始化完成,需要经历这三步:

![三分恶面渣逆袭:Bean初始化步骤](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-867066f1-49d1-4e57-94f9-4c66a3a8797e.png)

注入发生在第二步,**属性赋值**Spring 可以在这一步通过**三级缓存**来解决了循环依赖:

1. 一级缓存 : `Map<String,Object>` **singletonObjects**,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
2. 二级缓存 : `Map<String,Object>` **earlySingletonObjects**,早期曝光对象,用于保存实例化完成的 bean 实例
3. 三级缓存 : `Map<String,ObjectFactory<?>>` **singletonFactories**,早期曝光对象工厂,用于保存 bean 创建工厂,以便后面有机会创建代理对象。
1. 一级缓存:用于存放完全初始化好的单例 Bean
2. 二级缓存:用于存放正在创建但未完全初始化的 Bean 实例。
3. 三级缓存:用于存放 Bean 工厂对象,用于提前暴露 Bean

![三分恶面渣逆袭:三级缓存](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-01d92863-a2cb-4f61-8d8d-30ecf0279b28.png)

我们来看一下三级缓存解决循环依赖的过程:

AB 两个类发生循环依赖时:
#### 三级缓存解决循环依赖的过程是什么样的?

假如 AB 两个类发生循环依赖:

![三分恶面渣逆袭:循环依赖](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-cfc09f84-f8e1-4702-80b6-d115843e81fe.png)

A 实例的初始化过程:

①、创建 A 实例,实例化的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道。
①、创建 A 实例,实例化的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然这个对象还不完整,但是先曝光出来让大家知道。

![三分恶面渣逆袭:A 对象工厂](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-1a8bdc29-ff43-4ff4-9b61-3eedd9da59b3.png)

②、A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B
②、A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B

③、同样,B 注⼊属性时发现依赖 A,它就从缓存里找 A 对象。依次从⼀级到三级缓存查询 A

Expand All @@ -1257,14 +1252,10 @@ A 实例的初始化过程:

④、接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存

⑤、最后,⼀级缓存中保存着实例化、初始化都完成的 AB 对象
⑤、最后,⼀级缓存中保存着实例化、初始化都完成的 AB 对象

![三分恶面渣逆袭:AB 都好了](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-022f7cb9-2c83-4fe9-b252-b02bd0fb2435.png)

到此,我们就知道为什么 Spring 能解决 setter 注入的循环依赖了,因为实例化和属性赋值是分开的,里面有操作的空间。

如果都是构造器注入的话,那么都得在实例化这一步完成注入,没有可操作的空间。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的小米 25 届日常实习一面原题:如何解决循环依赖?
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的百度面经同学 1 文心一言 25 实习 Java 后端面试原题:Spring如何解决循环依赖?

Expand Down

0 comments on commit dee7ba6

Please sign in to comment.