Skip to content

Commit f45b948

Browse files
committed
Update Java Notes
1 parent 78f11d2 commit f45b948

File tree

3 files changed

+102
-41
lines changed

3 files changed

+102
-41
lines changed

DB.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8341,8 +8341,8 @@ user:id:3506728370 → {"name":"春晚","fans":12210862,"blogs":83}
83418341
83428342
当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型,具体需要满足两个条件:
83438343
8344-
- 当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认512个)
8345-
- 所有值都小于 hash-max-ziplist-value 配置(默认64字节)
8344+
- 当键值对个数小于 hash-max-ziplist-entries 配置(默认512个)
8345+
- 所有键值都小于 hash-max-ziplist-value 配置(默认64字节)
83468346
83478347
ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比 hashtable 更加优秀,当 ziplist 无法满足哈希类型时,Redis 会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而hashtable 的读写时间复杂度为 O(1)
83488348
@@ -8370,6 +8370,9 @@ ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在
83708370
83718371
Redis 字典使用散列表为底层实现,一个散列表里面有多个散列表节点,每个散列表节点就保存了字典中的一个键值对,发生哈希冲突采用链表法解决,存储无序
83728372
8373+
* 为了避免散列表性能的下降,当装载因子大于 1 的时候,Redis 会触发扩容,将散列表扩大为原来大小的 2 倍左右
8374+
* 当数据动态减少之后,为了节省内存,当装载因子小于 0.1 的时候,Redis 就会触发缩容,缩小为字典中数据个数的大约 2 倍大小
8375+
83738376
83748377
83758378
***
@@ -8384,7 +8387,7 @@ Redis 字典使用散列表为底层实现,一个散列表里面有多个散
83848387
83858388
数据存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序,允许重复元素
83868389
8387-
list类型:保存多个数据,底层使用**双向链表**存储结构实现,类似于 LinkedList
8390+
list 类型:保存多个数据,底层使用**双向链表**存储结构实现,类似于 LinkedList
83888391
83898392
<img src="https://gitee.com/seazean/images/raw/master/DB/list结构图.png" style="zoom:33%;" />
83908393
@@ -8464,7 +8467,14 @@ list类型:保存多个数据,底层使用**双向链表**存储结构实现
84648467
84658468
##### 底层结构
84668469
8467-
在 Redis3.2 版本以前列表类型的内部编码有两种:ziplist(压缩列表)和 linkedlist(链表),在 Redis3.2版本 以后对列表数据结构进行了改造,使用 quicklist(快速列表)代替了 ziplist 和 linkedlist
8470+
在 Redis3.2 版本以前列表类型的内部编码有两种:ziplist(压缩列表)和 linkedlist(链表)
8471+
8472+
列表中存储的数据量比较小的时候,列表就可以采用压缩列表的方式实现:
8473+
8474+
* 列表中保存的单个数据(有可能是字符串类型的)小于 64 字节
8475+
* 列表中数据个数少于 512 个
8476+
8477+
在 Redis3.2版本 以后对列表数据结构进行了改造,使用 quicklist(快速列表)代替了 ziplist 和 linkedlist
84688478
84698479
84708480
@@ -8612,11 +8622,11 @@ set类型:与hash存储结构完全相同,仅存储键不存储值(nil)
86128622

86138623
集合类型的内部编码有两种:
86148624

8615-
* intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用
8625+
* intset(整数集合):当集合中的元素都是整数且元素个数小于 set-maxintset-entries配置(默认512个)时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用
86168626

8617-
* hashtable(哈希表):当集合类型无法满足 intset 条件时,Redis会使用 hashtable 作为集合的内部实现
8627+
* hashtable(哈希表):当集合类型无法满足 intset 条件时,Redis 会使用 hashtable 作为集合的内部实现
86188628

8619-
整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构,可以保存类型为 int16_t、int32_t 或者 int64_t的整数值,并且保证集合中的元素是有序不重复的
8629+
整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构,可以保存类型为 int16_t、int32_t 或者 int64_t的整数值,并且保证集合中的元素是**有序不重复**
86208630

86218631

86228632

@@ -8716,7 +8726,7 @@ sorted_set类型:在set的存储结构基础上添加可排序字段,类似
87168726
当数据比较少时,有序集合使用的是 ziplist 存储的,使用 ziplist 格式存储需要满足以下两个条件:
87178727

87188728
- 有序集合保存的元素个数要小于 128 个;
8719-
- 有序集合保存的所有元素成员的长度都必须小于 64 字节
8729+
- 有序集合保存的所有元素大小都小于 64 字节
87208730

87218731

87228732

Issue.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110

111111
## System
112112

113-
### 进程线程
113+
### 操作系统
114114

115115
* 操作系统?
116116

@@ -120,7 +120,15 @@
120120

121121
* 什么是系统调度?
122122

123-
在用户程序中调用操作系统提供的核心态级别的子功能,结合用户态和核心态区别回答,一般使用陷入(trap),按调用功能分为:设备管理、文件管理、进程控制、进程通信、内存管理,
123+
在用户程序中调用操作系统提供的核心态级别的子功能,结合用户态和核心态区别回答,一般使用陷入(trap),按调用功能分为:设备管理、文件管理、进程控制、进程通信、内存管理
124+
125+
126+
127+
***
128+
129+
130+
131+
### 进程线程
124132

125133
* 进程线程?
126134

@@ -167,7 +175,34 @@
167175
* down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断
168176
* 如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex)
169177

170-
管程:Java中的synchronized
178+
管程:Java 中的 synchronized
179+
180+
* 进程状态转换:
181+
![](https://gitee.com/seazean/images/raw/master/Issue/OS-进程状态转换.jpg)
182+
183+
* 死锁问题:
184+
185+
预防死锁:
186+
187+
* 破坏互斥条件:有些资源必须互斥使用,无法破环互斥条件
188+
* 破坏不剥夺条件:增加系统开销,降低吞吐量
189+
* 破坏请求和保持条件:严重浪费系统资源,还可能导致饥饿现象
190+
* 破坏循环等待条件:浪费系统资源,并造成编程不便
191+
192+
避免死锁:
193+
194+
* 安全状态:能找到一个分配资源的序列能让所有进程都顺序完成
195+
* 银行家算法:采用预分配策略检查分配完成时系统是否处在安全状态
196+
197+
检测死锁:利用死锁定理化简资源分配图以检测死锁的存在
198+
199+
解除死锁:
200+
201+
* 资源剥夺法:挂起某些死锁进程并抢夺它的资源,以便让其他进程继续推进
202+
* 撤销进程法:强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源
203+
* 进程回退法:让一个或多个进程回退到足以回避死锁的地步
204+
205+
171206

172207

173208

Java.md

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2268,11 +2268,9 @@ public class Kmp {
22682268
public UF(int N) {
22692269
//初始化分组数量
22702270
this.count = N;
2271-
22722271
//初始化eleAndGroup数量
22732272
this.eleAndGroup = new int[N];
2274-
2275-
//初始化eleAndGroup中的元素及其所在分组的标识符,eleAndGroup索引作为并查集的每个节点的元素
2273+
//初始化eleAndGroup中的元素及其所在分组的标识符,eleAndGroup索引作为每个节点的元素
22762274
//每个索引处的值就是该组的索引,就是该元素所在的组的标识符
22772275
for (int i = 0; i < eleAndGroup.length; i++) {
22782276
eleAndGroup[i] = i;
@@ -2309,7 +2307,7 @@ public class Kmp {
23092307
}
23102308
}
23112309
```
2312-
2310+
23132311
* 测试代码:
23142312

23152313
```java
@@ -2358,6 +2356,7 @@ public int findRoot(int p) {
23582356
while (p != eleAndGroup[p]) {
23592357
p = eleAndGroup[p];
23602358
}
2359+
//p == eleGroup[p],说明p是根节点
23612360
return p;
23622361
}
23632362

@@ -2447,9 +2446,9 @@ public class UF_Tree_Weighted {
24472446

24482447
##### 应用场景
24492448

2450-
并查集存储的每一个整数表示的是一个大型计算机网络中的计算机
2449+
并查集存储的每一个整数表示的是一个大型计算机网络中的计算机
24512450

2452-
* 可以通过 connected(int p, int q) 来检测该网络中的某两台计算机之间是否连通
2451+
* 可以通过 connected(int p, int q) 来检测该网络中的某两台计算机之间是否连通
24532452
* 可以调用 union(int p,int q) 使得 p 和 q 之间连通,这样两台计算机之间就可以通信
24542453

24552454
畅通工程:某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府畅通工程的目标是使全省任何两个城镇间都可以实现交通,但不一定有直接的道路相连,只要互相间接通过道路可达即可,问最少还需要建设多少条道路?
@@ -6652,6 +6651,11 @@ HashMap继承关系如下图所示:
66526651

66536652
原因:当数组长度很小,假设是16,那么 n-1即为 1111 ,这样的值和hashCode()直接做按位与操作,实际上只使用了哈希值的后4位。如果当哈希值的高位变化很大,低位变化很小,就很容易造成哈希冲突了,所以这里**把高低位都利用起来,让高16位也参与运算**,从而解决了这个问题
66546653

6654+
哈希冲突的处理方式:
6655+
6656+
* 开放定址法:线性探查法(ThreadLocalMap部分详解),平方探查法(i + 1^2、i - 1^2、i + 2^2……)、双重散列(多个哈希函数)
6657+
* 链地址法:拉链法
6658+
66556659

66566660

66576661
2. put
@@ -15041,7 +15045,7 @@ Serial GC、Parallel GC、Concurrent Mark Sweep GC这三个GC不同:
1504115045
unused(25+1) + hash(31) + age(4) + lock(3) = 64bit #64位系统
1504215046
```
1504315047

15044-
* Klass Word:类型指针,对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;在64位系统中,开启指针压缩(-XX:+UseCompressedOops)或者JVM堆的最大值小于32G,这个指针也是4byte,否则是8byte
15048+
* Klass Word:类型指针,对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;在64位系统中,开启指针压缩 (-XX:+UseCompressedOops) 或者 JVM 堆的最大值小于 32G,这个指针也是 4byte,否则是 8byte
1504515049

1504615050
```ruby
1504715051
|-----------------------------------------------------|
@@ -15060,10 +15064,12 @@ Serial GC、Parallel GC、Concurrent Mark Sweep GC这三个GC不同:
1506015064
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
1506115065
|-----------------------|-----------------------------|-------------------------|
1506215066
```
15067+
15068+
![](https://gitee.com/seazean/images/raw/master/Java/JVM-对象头结构.png)
1506315069

1506415070
实例数据:实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来
1506515071

15066-
对齐填充:Padding 起占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,就是对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐时,就需要通过对齐填充来补全
15072+
对齐填充:Padding 起占位符的作用。64位系统,由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是8字节的整数倍,就是对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐时,就需要通过对齐填充来补全
1506715073

1506815074
32位系统
1506915075

@@ -15114,7 +15120,7 @@ Serial GC、Parallel GC、Concurrent Mark Sweep GC这三个GC不同:
1511415120

1511515121
#### 对象访问
1511615122

15117-
JVM是通过栈帧中的对象引用访问到其内部的对象实例:(内部结构查看类加载部分)
15123+
JVM是通过栈帧中的对象引用访问到其内部的对象实例:
1511815124

1511915125
* 句柄访问
1512015126
使用该方式,Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息
@@ -18570,16 +18576,24 @@ LocalVariableTable:
1857018576

1857118577
#### 锁升级
1857218578

18573-
##### 偏向锁
18579+
##### 升级过程
1857418580

18575-
###### 优化
18576-
18577-
**synchronized是可重入、不公平的重量级锁**,所以可以对其进行优化
18581+
**synchronized 是可重入、不公平的重量级锁**,所以可以对其进行优化
1857818582

1857918583
```java
1858018584
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 //随着竞争的增加,只能锁升级,不能降级
1858118585
```
1858218586

18587+
![](https://gitee.com/seazean/images/raw/master/Java/JUC-锁升级过程.png)
18588+
18589+
18590+
18591+
***
18592+
18593+
18594+
18595+
##### 偏向锁
18596+
1858318597
偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程之后重新获取该锁不再需要同步操作:
1858418598

1858518599
* 当锁对象第一次被线程获得的时候进入偏向状态,标记为101,同时使用 CAS 操作将线程 ID 记录到Mark Word。如果 CAS 操作成功,这个线程以后进入这个锁相关的同步块,查看这个线程 ID 是自己的就表示没有竞争,就不需要再进行任何同步操作
@@ -18593,19 +18607,17 @@ LocalVariableTable:
1859318607
* 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的
1859418608
thread、epoch、age 都为 0
1859518609
* 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数 `-XX:BiasedLockingStartupDelay=0` 来禁用延迟
18596-
* 如果禁用了偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,**第一次用到 hashcode 时才会赋值**,添加 VM 参数 `-XX:-UseBiasedLocking` 禁用偏向锁
1859718610

18598-
18599-
18600-
###### 撤销
18611+
JDK 8 延迟 4s 开启偏向锁原因:在刚开始执行代码时,会有好多线程来抢锁,如果开偏向锁效率反而降低
18612+
* 如果禁用了偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,**第一次用到 hashcode 时才会赋值**,添加 VM 参数 `-XX:-UseBiasedLocking` 禁用偏向锁
1860118613

1860218614
撤销偏向锁的状态:
1860318615

18604-
* 调用对象的hashCode:偏向锁的对象 MarkWord 中存储的是线程 id,调用 hashCode导致偏向锁被撤销
18616+
* 调用对象的 hashCode:偏向锁的对象 MarkWord 中存储的是线程 id,调用 hashCode 导致偏向锁被撤销
1860518617
* 轻量级锁会在锁记录中记录 hashCode
1860618618
* 重量级锁会在 Monitor 中记录 hashCode
1860718619
* 当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
18608-
* 调用wait/notify
18620+
* 调用 wait/notify
1860918621

1861018622

1861118623

@@ -18659,7 +18671,7 @@ public static void method2() {
1865918671
* 如果CAS失败,有两种情况:
1866018672

1866118673
* 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
18662-
* 如果是自己执行了synchronized锁重入,就添加一条 Lock Record 作为重入的计数
18674+
* 如果是自己执行了 synchronized 锁重入,就添加一条 Lock Record 作为重入的计数
1866318675

1866418676
![](https://gitee.com/seazean/images/raw/master/Java/JUC-轻量级锁原理3.png)
1866518677

@@ -18692,10 +18704,14 @@ public static void method2() {
1869218704

1869318705

1869418706

18707+
18708+
1869518709
***
1869618710

1869718711

1869818712

18713+
#### 锁优化
18714+
1869918715
##### 自旋锁
1870018716

1870118717
**重量级锁竞争**时,尝试获取锁的线程不会立即阻塞,可以使用**自旋**来进行优化,采用循环的方式去尝试获取锁
@@ -18780,8 +18796,6 @@ public class SpinLock {
1878018796

1878118797

1878218798

18783-
#### 锁优化
18784-
1878518799
##### 锁消除
1878618800

1878718801
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除
@@ -18881,10 +18895,10 @@ class BigRoom {
1888118895

1888218896
java 死锁产生的四个必要条件:
1888318897

18884-
1. 互斥条件,即当资源被一个线程使用(占有)时,别的线程不能使用
18885-
2. 不剥夺条件,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
18898+
1. 互斥条件,即当资源被一个线程使用(占有)时,别的线程不能使用
18899+
2. 不可剥夺条件,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
1888618900
3. 请求和保持条件,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
18887-
4. 循环等待条件,即存在一个等待循环队列:p1要p2的资源,p2要p1的资源,形成了一个等待环路
18901+
4. 循环等待条件,即存在一个等待循环队列:p1 要 p2 的资源,p2 要 p1 的资源,形成了一个等待环路
1888818902

1888918903
四个条件都成立的时候,便形成死锁。死锁情况下打破上述任何一个条件,便可让死锁消失
1889018904

@@ -18960,7 +18974,7 @@ class HoldLockThread implements Runnable {
1896018974

1896118975
定位死锁的方法:
1896218976

18963-
* 使用 jps 定位进程 id,再用 `jstack id`定位死锁,找到死锁的线程去查看源码,解决优化
18977+
* 使用 jps 定位进程 id,再用 `jstack id` 定位死锁,找到死锁的线程去查看源码,解决优化
1896418978

1896518979
```sh
1896618980
"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting formonitor entry [0x000000001f54f000]
@@ -18994,11 +19008,11 @@ class HoldLockThread implements Runnable {
1899419008
at thread.TestDeadLock$$Lambda$1/495053715
1899519009
```
1899619010

18997-
* linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 `top -Hp 进程id` 来定位是哪个线程,最后再用 jstack <pid>的输出来看各个线程栈,
19011+
* linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 `top -Hp 进程id` 来定位是哪个线程,最后再用 jstack <pid>的输出来看各个线程栈,
1899819012

1899919013
* 避免死锁:避免死锁要注意加锁顺序
1900019014

19001-
* 也可以使用 jconsole 工具,在 `jdk\bin` 目录下
19015+
* 可以使用 jconsole 工具,在 `jdk\bin` 目录下
1900219016

1900319017

1900419018

@@ -20742,7 +20756,7 @@ CAS底层实现是在一个循环中不断地尝试修改目标值,直到修
2074220756

2074320757
##### 分段机制
2074420758

20745-
分段CAS机制
20759+
分段 CAS 机制
2074620760

2074720761
* 在发生竞争时,创建Cell数组用于将不同线程的操作离散(通过hash等算法映射)到不同的节点上
2074820762
* 设置多个累加单元(会根据需要扩容,最大为CPU核数),Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1] 等,最后将结果汇总
@@ -21557,12 +21571,14 @@ static class Entry extends WeakReference<ThreadLocal<?>> {
2155721571

2155821572
```
2155921573

21560-
ThreadLocalMap使用**线性探测法来解决哈希冲突**:
21574+
ThreadLocalMap 使用**线性探测法来解决哈希冲突**:
2156121575

2156221576
* 该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出
2156321577
* 在探测过程中 ThreadLocal 会释放 key 为 NULL,value 不为 NULL 的脏 Entry对象,防止内存泄漏
2156421578
* 假设当前table长度为16,计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,如果还是冲突会回到0,取table[0],以此类推,直到可以插入,可以把Entry[] table看成一个**环形数组**
2156521579

21580+
线性探测法会出现**堆积问题**,一般采取平方探测法解决
21581+
2156621582
* 扩容:
2156721583

2156821584
rehash 会触发一次全量清理,如果数组长度大于等于长度的(2/3 * 3/4 = 1/2),则进行 resize

0 commit comments

Comments
 (0)