死锁

死锁模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class demo01 {

static Object o1 = new Object();
static Object o2 = new Object();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
});

t1.start();
t2.start();

}
}

分析

  1. 输入jps定位java进程

image-20240812140925419

  1. jstack 查看进程栈信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"Thread-0" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=89.15s tid=0x000001cffe8bf6e0 nid=13480 waiting for mo
nitor entry [0x00000010d83ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at day15.demo01.lambda$main$0(demo01.java:26)
- waiting to lock <0x00000007130789c0> (a java.lang.Object)
- locked <0x00000007130789b0> (a java.lang.Object)
at day15.demo01$$Lambda$14/0x0000000800c031f0.run(Unknown Source)
at java.lang.Thread.run(java.base@18.0.1.1/Thread.java:833)

"Thread-1" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=89.15s tid=0x000001cffe8bfba0 nid=12524 waiting for mo
nitor entry [0x00000010d84ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at day15.demo01.lambda$main$1(demo01.java:38)
- waiting to lock <0x00000007130789b0> (a java.lang.Object)
- locked <0x00000007130789c0> (a java.lang.Object)
at day15.demo01$$Lambda$15/0x0000000800c033f8.run(Unknown Source)
at java.lang.Thread.run(java.base@18.0.1.1/Thread.java:833)

demo01的thread-0和thread-1都处于阻塞状态

jstack最后也很明确的输出:

发现一个死锁 thread-0 在等待一个 thread-1 所持有的object的资源

而 thread-1 在等待一个thread-0 所持有的object的资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock monitor 0x000001cfd97eccc0 (object 0x00000007130789c0, a java.lang.Object),
which is held by "Thread-1"

"Thread-1":
waiting to lock monitor 0x000001cfd97ec780 (object 0x00000007130789b0, a java.lang.Object),
which is held by "Thread-0"

at day15.demo01$$Lambda$14/0x0000000800c031f0.run(Unknown Source)
at java.lang.Thread.run(java.base@18.0.1.1/Thread.java:833)
"Thread-1":
at day15.demo01.lambda$main$1(demo01.java:38)
- waiting to lock <0x00000007130789b0> (a java.lang.Object)
- locked <0x00000007130789c0> (a java.lang.Object)
at day15.demo01$$Lambda$15/0x0000000800c033f8.run(Unknown Source)
at java.lang.Thread.run(java.base@18.0.1.1/Thread.java:833)

Found 1 deadlock.

定位到是demo01.java的38行后就可以去破坏死锁条件解决死锁。

OOM

OOM模拟

1
2
3
4
5
6
7
8
9
10
11
12
public class demo01 {

public static void main(String[] args) throws InterruptedException {

List<byte[]> list = new ArrayList<>();
while(true){
byte[] bytes = new byte[5_000_000];
list.add(bytes);
Thread.sleep(1000);
}
}
}

在启动前先添加JVN参数:

1
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\heap.hprof
  • Xms: 设置JVM初始堆内存的大小。
  • Xmx: 设置JVM最大堆内存的大小。
  • XX:+HeapDumpOnOutOfMemoryError: 发生OOM异常时生成heap dump文件
  • -XX:HeapDumpPath: dump文件保存位置

开始运行:

1
2
3
4
5
6
java.lang.OutOfMemoryError: Java heap space
Dumping heap to D:\heap.hprof ...
Heap dump file created [3253522 bytes in 0.015 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at day15.demo01.main(demo01.java:20)
Disconnected from the target VM, address: '127.0.0.1:3008', transport: 'socket'

分析

将生成的 heap.hprof 文件放入 jprofiler 中进行分析

image-20240812144553397

我们查看最大类对象可以发现是list中含有了太多的byte[]数组

MySQL死锁

模拟

打开两个cmd窗口模拟两个session执行

session1:

1
2
3
4
begin;
update user set id = 4 where id = 1;
-- 先执行到这去执行session2的操作
update user set id = 5 where id = 2;

session2:

1
2
3
4
begin;
update user set id = 6 where id = 2;
update user set id = 7 where id = 1;
-- 再去执行session1的后面的操作

当最后输入session1的操作后

1
2
mysql>  update user set id = 5 where id = 2;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

应该是MySQL的死锁检测检测出死锁了

当发生死锁是,我们可以用命令查看死锁日志

1
show engine innodb staus

查看死锁日志

2024-08-12 14:58:47 0x2b6c
* (1) TRANSACTION:
TRANSACTION 224533, ACTIVE 7 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 2
MySQL thread id 11, OS thread handle 7480, query id 33 localhost ::1 root updating
update user set id = 7 where id = 1

* (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 516 page no 4 n bits 80 index PRIMARY of table test.user trx id 224533 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
0: len 4; hex 80000002; asc ;;
1: len 6; hex 000000036d15; asc m ;;
2: len 7; hex 02000000d8166a; asc j;;
3: len 7; hex 7a65726f74776f; asc zerotwo;;
4: len 15; hex 7a65726f74776f403136332e636f6d; asc zerotwo@163.com;;
5: len 6; hex 313233343536; asc 123456;;

* (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 516 page no 4 n bits 80 index PRIMARY of table test.user trx id 224533 lock_mode X locks rec but not gap waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000036d10; asc m ;;
2: len 7; hex 01000000f5088b; asc ;;
3: len 7; hex 67696e636f6465; asc gincode;;
4: len 11; hex 47696e403136332e636f6d; asc Gin@163.com;;
5: len 6; hex 313233343536; asc 123456;;

* (2) TRANSACTION:
TRANSACTION 224528, ACTIVE 23 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 2
MySQL thread id 10, OS thread handle 14088, query id 34 localhost ::1 root updating
update user set id = 5 where id = 2

* (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 516 page no 4 n bits 80 index PRIMARY of table test.user trx id 224528 lock_mode X locks rec but not gap
Record lock, heap no 6 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000036d10; asc m ;;
2: len 7; hex 01000000f5088b; asc ;;
3: len 7; hex 67696e636f6465; asc gincode;;
4: len 11; hex 47696e403136332e636f6d; asc Gin@163.com;;
5: len 6; hex 313233343536; asc 123456;;

* (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 516 page no 4 n bits 80 index PRIMARY of table test.user trx id 224528 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
0: len 4; hex 80000002; asc ;;
1: len 6; hex 000000036d15; asc m ;;
2: len 7; hex 02000000d8166a; asc j;;
3: len 7; hex 7a65726f74776f; asc zerotwo;;
4: len 15; hex 7a65726f74776f403136332e636f6d; asc zerotwo@163.com;;
5: len 6; hex 313233343536; asc 123456;;

* WE ROLL BACK TRANSACTION (2)

TRANSACTIONS

Trx id counter 224539
Purge done for trx’s n:o < 224539 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
—-TRANSACTION 284102103522432, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
—-TRANSACTION 284102103521656, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
—-TRANSACTION 284102103520880, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
—-TRANSACTION 224533, ACTIVE 192 sec
3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 4
MySQL thread id 11, OS thread handle 7480, query id 33 localhost ::1 root

分析

  • 死锁的SQL语句
1
2
update user set id = 7 where id = 1
update user set id = 5 where id = 2

事务1和事务2都是lock_mode X locks rec but not gap waiting

都是排他锁中的记录锁

X锁:排他锁、又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

S锁:共享锁,又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

record Lock: 记录锁,只对当前记录进行上锁

Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。

Next-Key Lock:rec+gap,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

MySQl加锁原理

在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引