面试题第二季

并发与并行之间的区别?

并发相当于 多个线程来秒杀
并行 一边 吃饭 一边看电视

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』。

链接:https://www.zhihu.com/question/33515481/answer/58849148

https://www.zhihu.com/question/33515481

并发,指的是多个事情,在同一时间段内同时发生了。 并行,指的是多个事情,在同一时间点上同时发生了
https://cloud.tencent.com/developer/article/1424249

Lock 与 Synchronized 之间区别?

Lock 是接口
Synchronized 是关键字
自动和手动控制锁

volatile 是什么?

volatile 是 Java 虚拟机提供的轻量级的同步机制
保证可见性, 不保证原子性, 禁止指令重排

画出JVM内存模型

JMM内存模型

JMM 可见性, 原子性, 有序性
但是没有volatile是没有原子性的

本地内存就是工作内存

volatile 可见性实现

当 int number前面没有volatile关键字时, main线程会一直在while死循环中, myData.number的值没有得到及时更新
当有volatile的时候, 当number的值 , 在其他的线程工作内存修改之后, 就会拷贝回主内存, 并且其他线程是可见的, while不会死循环

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
package com.atguigu.thread;

import java.util.concurrent.TimeUnit;

class MyData {
volatile int number = 0;

public void addTo60() {
this.number = 60;
}
}

public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\tcome in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);
}, "AAA").start();

// 第2个线程就是我们的main线程
while (myData.number == 0) {
// main线程就一直在这里等待循环, 知道number值不再等于零
}

System.out.println(Thread.currentThread().getName() + "\t mission is over");
}
}

存在volatile运行结果

1
2
3
AAA	come in
AAA update number value: 60
main mission is over

配置javap插件

参考: https://blog.csdn.net/stormkai/article/details/120079797

通过idea配置javap -c命令来配置下javap命令
选择File->Settings->Tools->External Tools
点击+号

1
2
3
Program:$JDKPath$\bin\javap.exe
Arguments:-c $OutputPath$\$FileDirRelativeToSourcepath$\$FileNameWithoutAllExtensions$.class
Working directory:$ProjectFileDir$

volatile借助原子类AtomicInteger解决可见性

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.atguigu.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class MyData {
volatile int number = 0;

public void addTo60() {
this.number = 60;
}

/**
* 请注意, 此时number前面是加了volatile关键字修饰的, volatile不保证原子性
*/
public void addPlusPlus() {
number++;
}

AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic() {
atomicInteger.getAndIncrement();
}
}

public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myData.addPlusPlus();
myData.addMyAtomic();
}
}, String.valueOf(i)).start();
}

// 需要等待上面20个线程都全部计算完成后, 再用main线程取的最终的结果值看是多少?
while (Thread.activeCount() > 2) {
Thread.yield();
}

System.out.println(Thread.currentThread().getName() + "\t int type, finally number value: " + myData.number);
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type finally number value: " + myData.atomicInteger);
}

/**
* volatile可以保证可见性, 及时通知其他线程, 朱物理内存的值已经被修改
*/
private static void seeOKByVolatile() {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\tcome in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);
}, "AAA").start();

// 第2个线程就是我们的main线程
while (myData.number == 0) {
// main线程就一直在这里等待循环, 知道number值不再等于零
}

System.out.println(Thread.currentThread().getName() + "\t mission is over");
}
}

指令重排

多线程环境中线程交替执行, 由于编译器优化重排的存在,
两个线程中使用的变量能否保证一致性是无法确定的, 结果无法预测

总结: 工作内存与主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决, 它们都可以使一个线程修改后的变量立即对其他线程可见

对于指令重排导致的可见性问题和有序性问题
可以利用volatile关键字解决, 因为volatile的另外一个作用就是禁止重排序优化

单例模式-synchronized

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
package com.atguigu.thread;

public class SingletonDemo {
private static SingletonDemo instance;

private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t构造方法SingletonDemo()" );
}

public synchronized static SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}

public static void main(String[] args) {
// 单线程 (main线程的操作动作......)
/*
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
*/

// 并发多线程后, 情况发生了很大的变化
for(int i = 1; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}


}
}

单例模式-volatile

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
37
38
39
40

package com.atguigu.thread;

public class SingletonDemo {
private volatile static SingletonDemo instance;

private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t构造方法SingletonDemo()");
}

public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}

public static void main(String[] args) {
// 单线程 (main线程的操作动作......)
/*
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
*/

// 并发多线程后, 情况发生了很大的变化
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}


}
}

CAS

比较并交换

真实值与期望值相比较, 如果相同 , 修改值, 如果不同 , 不修改值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.atguigu.thread;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);

// compareAndSet 修改值成功返回true, 修改失败返回false
System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data: " + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data: " + atomicInteger.get());
}
}

atomicInteger底层源码 CAS 和 Unsafe类(Unsafe中的方法大部分都是native的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
atomicInteger.getAndIncrement()

public final int getAndIncrement() {
// valueOffset表示是地址偏移量
return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

CAS总结(CompareAndSwap):
比较当前工作内存中的和主内存中的值, 如果相同则执行规定操作, 否则继续比较知道主内存和工作内存中的值一致为止

CAS应用:
CAS有3个操作数, 内存值V, 旧的预期值A, 要修改的更新至B
当且仅当预期值A和内存值V相同时, 将内存值V修改为B, 否则什么都不做

CAS缺点

循环时间长开销很大
只能保证一个共享变量的原子操作
引出来ABA问题

ABA问题

CAS会导致”ABA问题”
CAS算法实现一个重要前提需要去除内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B.
然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。

尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

原子引用

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
package com.atguigu.thread;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.concurrent.atomic.AtomicReference;

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
String userName;
int age;
}

public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3", 22);
User li4 = new User("li4", 25);

AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);

System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());

}
}

如何解决ABA问题?

如何解决ABA问题, 使用版本号(时间戳stamp)

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.atguigu.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo { // ABA问题的解决 AtomicStampedReference
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

public static void main(String[] args) {
System.out.println("========== 以下是ABA问题的产生 ==========");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();

new Thread(() -> {
// 暂停 1 秒钟 t2 线程, 保证上面的 t1 线程完成了一次 ABA 操作
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "t2").start();

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("========== 以下是ABA问题的解决 ==========");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t" + "第 1 次版本号: " + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, stamp, atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t" + "第 2 次版本号: " + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t" + "第 3 次版本号: " + atomicStampedReference.getStamp());
}, "t3").start();

new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t" + "第 1 次版本号: " + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 2024, stamp, atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t" + "修改成功否: " + b + "当前最新实际版本号: " + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t" + "当前实际最新值: " + atomicStampedReference.getReference());
}, "t4").start();
}
}

运行结果:

ArrayList容器不安全? 以及保证List安全

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.atguigu.thread;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class ContainerNotSafeDemo {
public static void main(String[] args) {
// List<String> list = new ArrayList<>(); // ConcurrentModificationException
// List<String> list = new Vector<>(); // use synchronized
// List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();

for(int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}

/**
* 不要只是会用, 会用只不过是一个API调用工程师, 底层原理?
*
* 1.故障现象
* java.util.ConcurrentModificationException
* 2.导致原因
* 并发争抢修改导致, 参考我们的花名册签名情况
* 一个人正在写入, 另外一个同学过来抢夺, 导致数据不一致异常, 并发修改异常
* 3.解决方案
* 3.1 new Vector<>();
* 3.2 Collections.synchronizedList(new ArrayList<>());
* 3.3 new CopyOnWriteArrayList<>();
* 4.优化建议(同样的错误不犯第 2 次)
* 写时复制
* CopyOnWrite容器即写时复制的容器,一个容器添加元素的时候,不直接往当前容object[]添加,而是先将当前容器object[]进行Copy,
* 复制出一个新的容器object[] newElements,然后新的容器object[] newElements里添加元素,添加完元素之后
* 再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读.
* 而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
*
* Appends the specified element to the end of this list.
* Params:
* e – element to be appended to this list
* Returns:
* true (as specified by Collection.add)
*
* public boolean add(E e) {
* final ReentrantLock lock = this.lock;
* lock.lock();
* try {
* Object[] elements = getArray();
* int len = elements.length;
* Object[] newElements = Arrays.copyOf(elements, len + 1);
* newElements[len] = e;
* setArray(newElements);
* return true;
* } finally {
* lock.unlock();
* }
* }
*
*/
}

拓展 set map

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
package com.atguigu.thread;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

public class ContainerNotSafeDemo {
public static void main(String[] args) {
// Map<String, String> map = new HashMap<>(); // java.util.ConcurrentModificationException
// Map<String, String> map = new ConcurrentHashMap<>();
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
for(int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put( Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}

private static void setNotSafe() {
// Set<String> set = new HashSet<>(); // java.util.ConcurrentModificationException
Set <String> set = new CopyOnWriteArraySet<>();
for(int i = 1; i <= 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
new HashSet<Integer>().add(1);
}
}

一道传参练习题

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
37
38
39
40
41
42
43
44
45
46
package com.atguigu.thread;

import lombok.Data;

@Data
class Person {
private Integer id;
private String personName;

public Person(String personName) {
this.personName = personName;
}
}

public class TestTransferValue {
public void changeValue1(int age) {
age = 30;
}

public void changeValue2(Person person) {
person.setPersonName("xxx");
}

public void changeValue3(String str) {
str = "xxx";
}

public static void main(String[] args) {
TestTransferValue test = new TestTransferValue();
int age = 20;
test.changeValue1(age);
System.out.println("age = " + age);

Person person = new Person("abc");
test.changeValue2(person);
System.out.println("person = " + person);

String str = "abc";
// 常量池, 池子中没有就新建了一个, main方法中引用的str没有发生变化
// changeValue3引用的str发生了变化, 但也仅仅只是发生了变化
test.changeValue3(str);
System.out.println("str = " + str);
}

}

公平锁与非公平锁

公平锁 是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁 是指多个线程获取锁的顺序并不是按照中请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁
在高并发的情况下,有可能会造成优先级反转或者饥饿现象

Java ReentrantLock而言
通过构造函数指定该锁是否是公平锁, 默认就是非公平锁. 非公平锁的优点在于吞吐量比公平锁大

对于Synchronized而言, 也是一种非公平锁

可重入锁(又名递归锁)

可重入锁(又名递归锁)

指的是同一线程外层函数获得锁之后 ,内层递归函数仍然能获取该锁的代码,
在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。

锁中内部还有一把锁

ReentrantLock/Synchronized就是一个典型的可重入锁
可重入锁最大的作用就是避免死锁

java锁之可重入锁和递归锁代码验证

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.atguigu.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Phone implements Runnable {
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + "\t sendSMS()");
sendEmail();
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t sendEmail()");
}

@Override
public void run() {
get();
}

Lock lock = new ReentrantLock();
private void get() {
// 多把锁没有关系, 上了几把锁, 就需要解锁几把锁
lock.lock();
lock.lock();
lock.lock();
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t get()");
set();
} finally {
lock.unlock();
lock.unlock();
lock.unlock();
lock.unlock();
lock.unlock();
}
}

private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t set()");
} finally {
lock.unlock();
}
}
}

public class ReenterLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();

new Thread(() -> {
phone.sendSMS();
}, "t1").start();

new Thread(() -> {
phone.sendSMS();
}, "t2").start();

try {
TimeUnit.SECONDS.sleep(2);
System.out.println();
System.out.println();
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
}

Thread t3 = new Thread(phone, "t3");
t3.start();

Thread t4 = new Thread(phone, "t4");
t4.start();

}
}

自旋锁 (spinlock)

是指尝试获取锁的线程不会立即阳塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺
点是循环会消耗CPU

手写自旋锁

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.atguigu.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {

AtomicReference<Thread> atomicReference = new AtomicReference<>();

public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t myLock");
while (!atomicReference.compareAndSet(null, thread)) {

}
}

public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t myUnLock");
atomicReference.compareAndSet(thread, null);
}

public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
spinLockDemo.myUnLock();
}, "t1").start();

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
spinLockDemo.myUnLock();
}, "t2").start();
}
}

读写锁

独占锁: 指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可被多个线程所持有。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

未使用读写锁暴露出来的问题

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.atguigu.thread;

import java.util.HashMap;
import java.util.Map;

/**
* 资源类
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();

public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "\t 正在写入: " + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成");
}

public void get(String key) {
System.out.println(Thread.currentThread().getName() + "\t 正在读取");
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成: " + result);
}

public void clear() {
map.clear();
}

}

public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache mycache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int finalInt = i;
new Thread(() -> {
mycache.put(finalInt + "", finalInt + "");
}, String.valueOf(i)).start();
}

for (int i = 1; i <= 5; i++) {
final int finalInt = i;
new Thread(() -> {
mycache.get(finalInt + "");
}, String.valueOf(i)).start();
}

}
}

/*
未加锁 运行结果:
很明显, 5在写入过程中被4打断了, 这并不是我们想要的
理想情况 , 应该要5写入完成, 才能给下一个线程写入
2 正在写入: 2
2 写入完成
5 正在写入: 5
4 正在写入: 4
4 写入完成
1 正在写入: 1
3 正在写入: 3
1 写入完成
5 写入完成
2 正在读取
3 写入完成
2 读取完成: 2
3 正在读取
1 正在读取
3 读取完成: 3
4 正在读取
4 读取完成: 4
1 读取完成: 1
5 正在读取
5 读取完成: 5
*/

CountDownLatch

让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒
CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞
其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.atguigu.thread;

import lombok.Getter;

import java.util.concurrent.CountDownLatch;

enum CountryEnum {

ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");

@Getter
private Integer retCode;
@Getter
private String retMessage;

CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}

public static CountryEnum forEach_CountryEnum(int index) {
for (CountryEnum value : CountryEnum.values()) {
if (value.getRetCode() == index) {
return value;
}
}

return null;
}
}

public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "国, 被灭");
countDownLatch.countDown();
}, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
}

countDownLatch.await();

System.out.println(Thread.currentThread().getName() + "秦帝国, 一统华夏");

// 小复习
System.out.println();
System.out.println(CountryEnum.ONE);
System.out.println(CountryEnum.ONE.getRetCode());
System.out.println(CountryEnum.ONE.getRetMessage());
}

private static void closeDoor() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t上完晚自习, 离开教室");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}

countDownLatch.await();

System.out.println(Thread.currentThread().getName() + "班长最后关门走人");
}
}

/*
1-CountDownLatch:未使用CountDownLatch
1 上完晚自习, 离开教室
5 上完晚自习, 离开教室
4 上完晚自习, 离开教室
main班长最后关门走人
3 上完晚自习, 离开教室
2 上完晚自习, 离开教室
6 上完晚自习, 离开教室

2-CountDownLatch:使用CountDownLatch
2 上完晚自习, 离开教室
1 上完晚自习, 离开教室
3 上完晚自习, 离开教室
5 上完晚自习, 离开教室
4 上完晚自习, 离开教室
6 上完晚自习, 离开教室
main班长最后关门走人

3-CountDownLatch: 使用CountDownLatch & 枚举类的应用
齐国, 被灭
楚国, 被灭
燕国, 被灭
赵国, 被灭
魏国, 被灭
韩国, 被灭
main秦帝国, 一统华夏

ONE
1


*/

CyclicBarrier

CyclicBarrier Cyclic循环 Barrier屏障

CyclicBarrier的字面意思是可循环 (Cyclic) 使用的屏障 (Barrier)。
它要做的事情是,让一组线程到达一个屏障(也可以叫同步点) 时被阻塞,直到最后一个线程到达屏障时,
屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法

集齐7颗龙珠就能召唤神龙

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
package com.atguigu.thread;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for(int i = 1; i <= 7; i++) {
new Thread(() -> {
System.out.println("收集到第:" + Thread.currentThread().getName() + "\t龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}, String.valueOf(i)).start();
}
}
}

/*
运行结果:
收集到第:4 龙珠
收集到第:7 龙珠
收集到第:1 龙珠
收集到第:3 龙珠
收集到第:5 龙珠
收集到第:2 龙珠
收集到第:6 龙珠
召唤神龙
*/

Semaphore

Semaphore 信号量 , 信号灯

信号量主要用于两个目的, 一个是用于多个共享资源的互斥使用, 另一个用于并发线程数的控制

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
37
38
39
40
41
42
43
44
45
package com.atguigu.thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 模拟3个停车位

for(int i = 1; i <= 6; i++) { // 模拟6部汽车
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t抢到车位");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "\t停车3秒后离开车位");

} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
/*
运行结果:
1 抢到车位
3 抢到车位
2 抢到车位
3 停车3秒后离开车位
2 停车3秒后离开车位
1 停车3秒后离开车位
4 抢到车位
5 抢到车位
6 抢到车位
4 停车3秒后离开车位
6 停车3秒后离开车位
5 停车3秒后离开车位
*/

BlockQueue

阻塞队列

*试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
同样
试图往已满的阻寨队列中添加新元素的线程同样也会被阻寨,直到其他的线程从列中移除一个或者多个元素或者完全清空队
列后使队列重新变得空闲起来并后续新增

在多线程领域: 所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒

为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了

在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.atguigu.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

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

// 1.阻塞队列-抛出异常组 add element remove
/*
// List<String> list = new ArrayList<>();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("d"));

System.out.println(blockingQueue.element());

System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException
// System.out.println(blockingQueue.remove());
*/

// 2.阻塞队列-返回布尔值 add element remove
/*
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("x"));

System.out.println(blockingQueue.peek());

System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
*/

// 3.阻塞队列-阻塞 put take
/*
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
System.out.println("===");
// blockingQueue.put("x");

blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
// blockingQueue.take();
*/

// 4.阻塞队列-超时控制
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
// System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));

}
}

SynchronousQueue

阻塞队列之同步SynchronousQueue队列

SynchronousQueue没有容量
与其他BlockingQueue不同, SynchronousQueue是一个不存储元素的BlockingQueue
每一个put操作必须要等待一个take操作, 否则不能继续添加元素, 反之亦然

synchronousQueue 生产一个阻塞等待消费一个, 并不会存储第二个, 只有第一个消费掉了, 才会开始生产第二个

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.atguigu.thread;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class SynchronousQueueDemo {
public static void main(String[] args) {
// synchronousQueue 生产一个阻塞等待消费一个, 并不会存储第二个, 只有第一个消费掉了, 才会开始生产第二个
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();

new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
synchronousQueue.put("1");

System.out.println(Thread.currentThread().getName() + "\t put 2");
synchronousQueue.put("2");

System.out.println(Thread.currentThread().getName() + "\t put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "AAA").start();

new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\t get" + synchronousQueue.take());

TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\t get" + synchronousQueue.take());

TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\t get" + synchronousQueue.take());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "BBB").start();
}
}

/*
运行结果:
AAA put 1
BBB get1
AAA put 2
BBB get2
AAA put 3
BBB get3
*/

线程通信之生产者消费者传统版

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package com.atguigu.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public void increment() throws Exception {
lock.lock();
try {
// 1. 判断
while (number != 0) {
// 等待, 不能生产
condition.await();
}
// 2.干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3.通知唤醒
condition.signalAll();
} finally {
lock.unlock();
}
}

public void decrement() throws Exception {
lock.lock();
try {
// 1. 判断
while (number == 0) {
// 等待, 不能生产
condition.await();
}
// 2.干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3.通知唤醒
condition.signalAll();
} finally {
lock.unlock();
}
}
}

/**
* 题目: 一个初始值为零的变量, 两个线程对其交替操作, 一个加1, 一个减1, 来5轮
*/
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, "AA").start();

new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
shareData.decrement();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, "BB").start();
/*
// 注意 : 资源类当中的判断不能够使用 if 来判断, 需要使用 while
// 为了避免多个线程出现问题, 使用 while 才能避免多个多线程造成的虚假唤醒问题
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, "CC").start();

new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
shareData.decrement();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, "DD").start();
*/
}
}

wait方法是什么类的? Object

Synchronized和Lock有什么区别

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.atguigu.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 题目: synchronized 和 Lock有什么区别? 用新的 lock 有什么好处? 你举例说说
* 1 原石构成
* synchronized是关键字属于JVM层面
* monitorenter (底层是通过monitor对象来完成, 其实wait/notify等方法也依赖于monitor对象只有在同步块或方法中才能调wait/notify等方法)
* monitorexit
* Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁
*
* 2 使用方法
* synchronized 不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
* ReentrantLock则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。
* 需要lock()和unlock()方法虎合try/finally 语何块来完成。
*
* 3 等待是否可中断
* synchronized不可中断,除非抛出导常或青正常运行完成
* ReentrantLock 可中断,
* 1.设置超时方法 tryLock(lona timeout, TimeUnit unit)
* 2.LockInterruptibly()放代码块中,调用interrupt() 方法可中断
*
* 4 加锁是否公平
* synchronized非公平锁
* ReentrantLock两青都可以,默认非公平锁,构造方法可以传入boolean,true 为公平锁,faLse 为非公平锁
*
* 5 锁绑定多个条件Condition
* synchronized没有
* ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程
*
*
*
*
* 题目: 多线程之间按顺序调用, 实现 A -> B -> C 三个线程启动, 要求如下
* AA 打印 5 次, BB 打印 10次 , CC 打印 15 次
* 紧接着
* AA 打印 5 次, BB 打印 10次 , CC 打印 15 次
* ...
* 来 10 轮
*/
public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
synchronized (new Object()) {

}

Lock lock = new ReentrantLock();
}
}

锁绑定多个条件Condition

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.atguigu.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 题目: synchronized 和 Lock有什么区别? 用新的 lock 有什么好处? 你举例说说
* 1 原石构成
* synchronized是关键字属于JVM层面
* monitorenter (底层是通过monitor对象来完成, 其实wait/notify等方法也依赖于monitor对象只有在同步块或方法中才能调wait/notify等方法)
* monitorexit
* Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁
*
* 2 使用方法
* synchronized 不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
* ReentrantLock则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。
* 需要lock()和unlock()方法虎合try/finally 语何块来完成。
*
* 3 等待是否可中断
* synchronized不可中断,除非抛出导常或青正常运行完成
* ReentrantLock 可中断,
* 1.设置超时方法 tryLock(lona timeout, TimeUnit unit)
* 2.LockInterruptibly()放代码块中,调用interrupt() 方法可中断
*
* 4 加锁是否公平
* synchronized非公平锁
* ReentrantLock两青都可以,默认非公平锁,构造方法可以传入boolean,true 为公平锁,faLse 为非公平锁
*
* 5 锁绑定多个条件Condition
* synchronized没有
* ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程
*
*
*
*
* 题目: 多线程之间按顺序调用, 实现 A -> B -> C 三个线程启动, 要求如下
* AA 打印 5 次, BB 打印 10次 , CC 打印 15 次
* 紧接着
* AA 打印 5 次, BB 打印 10次 , CC 打印 15 次
* ...
* 来 10 轮
*/

class ShareResource {
private int number = 1;
private Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();

public void print5() {
lock.lock();
try {
while (number != 1) {
try {
c1.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 2;
c2.signal();
} finally {
lock.unlock();
}
}

public void print10() {
lock.lock();
try {
while (number != 2) {
try {
c2.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 3;
c3.signal();
} finally {
lock.unlock();
}
}

public void print15() {
lock.lock();
try {
while (number != 3) {
try {
c3.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 1;
c1.signal();
} finally {
lock.unlock();
}
}
}

public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print5();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print10();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print15();
}
}, "C").start();


}
}

线程通信之生产者消费者阻塞队列版

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.atguigu.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class MyResource {
private volatile boolean FLAG = true; // 默认开启, 进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();

BlockingQueue<String> blockingQueue = null;

public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}

public void myProd() throws Exception {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t大老板叫停了, 表示FLAG = false, 生产动作结束");
}

public void myConsumer() throws Exception {
String result = null;
while (FLAG) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (null == result || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t超过2秒钟没有取到蛋糕, 消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列蛋糕" + result + "成功");
}
}

public void stop() {
this.FLAG = false;
}
}

public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
// MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
MyResource myResource = new MyResource(new LinkedBlockingQueue<>(10)); // 接口参数: 可以传递不同类型的阻塞队列 适配器模式 new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
try {
myResource.myProd();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
System.out.println();
System.out.println();
try {
myResource.myConsumer();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "Consumer").start();

try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println();
System.out.println();
System.out.println();

System.out.println("5秒钟时间到, 大老板main线程叫停, 活动结束");
myResource.stop();

}
}

线程池

线程池使用及优势

为什么用线程池,优势

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

他的主要特点为:线程复用,控制最大并发数;管理线程

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

1
2
3
4
5
6
7
8
9
package com.atguigu.thread;

public class MyThreadPoolDemo {
public static void main(String[] args) {
// 获取电脑CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}

Java中线程池是通过Executor框架实现的, 该框架中用到了 Executor , Executors, ExecutorService, ThreadPoolExecutor这几个类

常用的几个创建线程池的方法

1
2
3
4
5
6
7
// 这两个不常用
// Executors.newScheduledThreadPool(int corePoolSize);
// Executors.newWorkStealingPool(int parallelism); // java8 新增, 使用目前机器上可用的处理器作为它的并行级别
// 以下三个常用
Executors.newFixedThreadPool(int parallelism);
Executors.newSingleThreadExecutor();
Executors.newCachedThreadPool();

线程池3个常用方式

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.atguigu.thread;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo {
public static void main(String[] args) {
// 获取电脑CPU核数
// System.out.println(Runtime.getRuntime().availableProcessors());

// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 一池5个处理线程
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 一池1个处理线程
ExecutorService threadPool = Executors.newCachedThreadPool(); // 一池N个处理线程
try {
// 模拟10个用户来办理业务, 每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 10; i++) {
// submit() execute() 之间区别就是返回值不同, 一个是 Future, 一个是 void
// threadPool.submit(() -> {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 办理业务");
});

/*
// 假设 newCachedThreadPool 当中的, 每个线程间隔一段时间才开始执行下一个任务
// newCachedThreadPool 只会有一个线程工作
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
*/

}
} finally {
threadPool.shutdown();
}
}
}























Executors.newFixedThreadPool(5); 执行长期任务, 性能好很多
Executors.newSingleThreadExecutor(); 一个任务一个任务执行的场景
Executors.newCachedThreadPool(); 适用: 执行很多短期异步的小程序或者负载较轻的服务器

以上三个方法的底层实现其实都是通过返回 ThreadPoolExecutor 对象实现的

创建线程的有哪几种方式?

创建线程的有哪几种方式?
(1) 继承Thread类
(2) 实现Runable接口, 没有返回值, 不抛异常, 实现run()方法
(3) 实现Callable接口, 有返回值, 抛异常, 实现call()方法 , 支持泛型
(4) Executor 线程池

线程池的几个重要参数介绍?

通过观察源码发现, 最终返回的都是 ThreadPoolExecutor() 方法
Ctrl B查看源码

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

发现 ThreadPoolExecutor 接收参数之后调用了 this, this构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

总共七个参数, 如下

说说线程池的底层工作原理?

线程池的4种拒绝策略你谈谈

是什么?
等待队列也已经排满了,再也塞不下新任务了司时,
线程池中的max线程也达到了,无法继续为新任务服务
这时候我们就需要拒绝策略机制合理的处理这个问题。

AbortPolicy(默认): 直接抛出 RejectedExecutionException 异常阻止系统正常运行
CallerRunsPolicy: “调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者, 从而降低新任务的流量
DiscardoldestPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
DiscardPolicy: 直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案

以上内置拒绝策略均实现了RejectedExecutionHandler接口

你在工作中单一的/固定数的/可变的三种创建线程池的方法, 你用的哪个多? 超级大坑

你在工作中单一的/固定数的/可变的三种创建线程池的方法, 你用的哪个多? 超级大坑
答案是一个都不用, 我们生产上只能使用自定义的
Executors中JDK已经给你提供好了, 为什么不用?

通过观察底层源码

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

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

// newFixedThreadPool 和 newSingleThreadExecutor 使用的都是LinkedBlockingQueue
// 这个 LinkedBlockingQueue的构造, 最大值是Integer.MAX_VALUE

public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}

// 而 newCachedThreadPool 的第二个参数 maximumPoolSize就是 Integer.MAX_VALUE
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

使用 Executors 创建出来的线程池, 都是无界的队列, 这可能会非常消耗资源

并且, 在阿里巴巴手册提及到

  1. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
    说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。
    如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
  2. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
    样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    说明:Executors 返回的线程池对象的弊端如下:
    1) FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    2) CachedThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

你在工作中是如何使用线程池的, 是否自定义过线程池使用?

使用 new ThreadPoolExecutor() 自定义线程池
触发拒绝策略的情况: 任务数量 > maximumPoolSize + capacity

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.atguigu.thread;

import java.util.concurrent.*;

public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
3,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy() // java.util.RejectedExecutionException
// new ThreadPoolExecutor.CallerRunsPolicy() // 将多余的任务退回给调用者(此时的调用者是main)
// new ThreadPoolExecutor.DiscardOldestPolicy() // 丢弃等待最长时间的任务
new ThreadPoolExecutor.DiscardPolicy() // 丢弃

);

try {
// 模拟10个用户来办理业务, 每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 办理业务");
});
}
} finally {
threadPool.shutdown();
}
}

private static void threadPoolInit() {
// 获取电脑CPU核数
// System.out.println(Runtime.getRuntime().availableProcessors());

// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 一池5个处理线程
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 一池1个处理线程
ExecutorService threadPool = Executors.newCachedThreadPool(); // 一池N个处理线程
try {
// 模拟10个用户来办理业务, 每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 10; i++) {
// submit() execute() 之间区别就是返回值不同, 一个是 Future, 一个是 void
// threadPool.submit(() -> {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 办理业务");
});

/*
// 假设 newCachedThreadPool 当中的, 每个线程间隔一段时间才开始执行下一个任务
// newCachedThreadPool 只会有一个线程工作
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
*/

}
} finally {
threadPool.shutdown();
}
}
}

合理配置线程池你是如何考虑的?

CPU密集型:

IO密集型:
1.

2.

死锁编码及定位分析

什么是死锁?

死锁产生的主要原因:
1.系统资源不足
2.进程运行推进的顺序不合适
3.资源分配不当

死锁demo

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.atguigu.thread;

import java.util.concurrent.TimeUnit;

class HoldLockThread implements Runnable {

private String lockA;
private String lockB;

public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}

@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有: " + lockA + "\t 尝试获得: " + lockB);

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有: " + lockB + "\t 尝试获得: " + lockA);
}
}
}
}

/**
* 死锁是指两个或两个以上的进程在执行过程中, 因争夺资源而造成的一种互相等待的现象, 若无外力干涉那它们都将无法推进下去
*/
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new HoldLockThread(lockA, lockB), "AAA").start();
new Thread(new HoldLockThread(lockB, lockA), "BBB").start();

/**
* linux ps -ef | grep xxx ls -l
*
* windows下java运行程序, 也有类似ps的查看进程的命令, 但是目前我们需要查看的只是java
* jps = java ps jps -l
*/
}
}

如何排查死锁?
jps命令定位进程号
jstack找到死锁查看

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
$ jps -l      
14352 org.jetbrains.jps.cmdline.Launcher
34612
35716 com.atguigu.thread.DeadLockDemo
14408 org.codehaus.classworlds.Launcher
12892 sun.tools.jps.Jps

$ jstack 35716
2024-01-10 01:36:35
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000000001e478800 nid=0x68d0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"BBB" #13 prio=5 os_prio=0 tid=0x000000001e474800 nid=0x460c waiting for monitor entry [0x000000001f27f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.atguigu.thread.HoldLockThread.run(DeadLockDemo.java:27)
- waiting to lock <0x000000076bba2fa8> (a java.lang.String)
- locked <0x000000076bba2fe0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

"AAA" #12 prio=5 os_prio=0 tid=0x000000001e472800 nid=0x3dac waiting for monitor entry [0x000000001f17f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.atguigu.thread.HoldLockThread.run(DeadLockDemo.java:27)
- waiting to lock <0x000000076bba2fe0> (a java.lang.String)
- locked <0x000000076bba2fa8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001e38c000 nid=0x186c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001e36d000 nid=0x618c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001e368000 nid=0x99c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001e362800 nid=0x7bfc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001e361800 nid=0x2300 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001e34e800 nid=0x33f8 runnable [0x000000001ea7e000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076ba85008> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076ba85008> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:53)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001e2b8000 nid=0x55a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001e314800 nid=0x2ec runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001e2a0800 nid=0x4748 in Object.wait() [0x000000001e77f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b908ec8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000076b908ec8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002a19000 nid=0x8a38 in Object.wait() [0x000000001e27f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b906b68> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076b906b68> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x000000001c3a9800 nid=0x1694 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002938000 nid=0x6590 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002939800 nid=0x24dc runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000293b000 nid=0x5930 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000293c800 nid=0x5d98 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000293f000 nid=0x4390 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002941000 nid=0x7950 runnable


Found one Java-level deadlock:
=============================
"BBB":
waiting to lock monitor 0x000000001c3b0d98 (object 0x000000076bba2fa8, a java.lang.String),
which is held by "AAA"
"AAA":
waiting to lock monitor 0x000000001c3b3158 (object 0x000000076bba2fe0, a java.lang.String),
which is held by "BBB"

Java stack information for the threads listed above:
===================================================
"BBB":
at com.atguigu.thread.HoldLockThread.run(DeadLockDemo.java:27)
- waiting to lock <0x000000076bba2fa8> (a java.lang.String)
- locked <0x000000076bba2fe0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"AAA":
at com.atguigu.thread.HoldLockThread.run(DeadLockDemo.java:27)
- waiting to lock <0x000000076bba2fe0> (a java.lang.String)
- locked <0x000000076bba2fa8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

常见的垃圾回收算法

0.JVM体系结构概述

1.引用计数

2.复制

3.标记清除

4.标记整理

JVM复习题

1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots

1.什么是垃圾?
简单的说就是内存中已经不再被使用到的空间就是垃圾

2.要进行垃圾回收, 如何判断一个对象是否可以被回收?
(1)引用计数法

(2)枚举根节点做可达性分析(根搜索路径)

a)case

b)Java中可以作为CG Roots的对象
1) 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表) 中引用的对象
2) 方法区中的类静态属性引用的对象。
3) 方法区中常量引用的对象。
4) 本地方法栈中JNI(Native方法)引用的对象

Person p1 = new Person();
左边: 引用 右边: 实例对象

2.你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值

  1. JVM的参数类型
    (1) 标配参数
    (1.1) -version
    (1.2) -help
    (1.3) java -showversion

    (2) X参数(了解)
    (2.1) -Xint 解释执行
    (2.2) -Xcomp 第一次使用就编译成本地代码
    (2.3) -Xmixed 混合模式 (先编译, 再执行)

    (3) XX参数
    (3.1) Boolean类型
    a) 公式 -XX: +或者 -某个属性值
    +表示开启 -表示关闭
    b) case:
    c) 是否打印GC收集细节
    c1) -XX: -PrintGCDetails
    c2) -XX: +PrintGCDetails
    d) 是否使用串行垃圾回收器
    d1) -XX: -UseSerialGC
    d2) -XX: +UseSerialGC

如何查看一个正在运行中的Java程序, 它的某个jvm参数是否开启? 具体指是多少?
jps jinfo

编写如下代码:

1
2
3
4
5
6
7
8
9
package com.atguigu.thread;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("HelloGC");
Thread.sleep(Integer.MAX_VALUE);
}
}

输入如下命令进行查看正在运行的java程序是否开启了某个jvm参数

1
2
$ jps -l
$ jinfo -flag PrintGCDetails 37860

在VM Options中添加参数 -XX:+PrintGCDetails , 再次查看此时运行的jvm参数, 变成了 +, 之前默认是 -

(3.2) KV设值类型
a) 公式 -XX:属性key=属性值value
b) Case
c) -XX:MetaspaceSize=128m
d) -XX:MaxTenuringThreshold=15

1
2
3
$ jps -l
$ jinfo -flag MetaspaceSize 29280
$ jinfo -flag MaxTenuringThreshold 29280

查看默认的 元空间大小 21807104 / 1024 / 1024 ≈ 21M

修改元空间大小, 设置VM options : -XX:MetaspaceSize=128m

同理可以查看 MaxTenuringThreshold 设置对象进入老年代的最大年龄阈值

(4) jinfo举例, 如何查看当前运行程序的配置
公式: jinfo -flag 配置项进程编号

1
2
3
jps
jinfo -flag 具体参数 java进程编号
jinfo -flags java进程编号



(5) 题外话(坑题)
两个经典参数: -Xms和-Xmx
这个你如何解释?
-Xms 等价于 -XX:InitialHeapSize
-Xmx 等价于 -XX:MaxHeapSize

  1. 判断家底查看JVM默认值
    (1) -XX:+PrintFlagsInitial
    主要查看初始默认
    公式 java -XX:+PrintFlagsInitial -version
    java -XX:+PrintFlagsInitial

(2) -XX:+PrintFlagsFinal
主要查看修改更新
公式 java -XX:+PrintFlagsFinal -version
公式 java -XX:+PrintFlagsFinal -version

:= 说明被人为的修改过了, =表示是默认值

(3) PrintFlagsFinal 举例, 运行java命令的同时打印参数

1
2
3
4
5
6
7
8
9
10
11
package com.atguigu.thread;

public class T {
public static void main(String[] args) {
int a = 100;
int b = 200;
int retValue = a + b;
System.out.println("retValue = " + retValue);
}
}

编译运行

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
PS E:\Code\面试题\大厂面试题第2季\src\main\java\com\atguigu\thread> pwd

Path
----
E:\Code\面试题\大厂面试题第2季\src\main\java\com\atguigu\thread

PS E:\Code\面试题\大厂面试题第2季\src\main\java\com\atguigu\thread> more .\T.java
package com.atguigu.thread;

public class T {
public static void main(String[] args) {
int a = 100;
int b = 200;
int retValue = a + b;
System.out.println("retValue = " + retValue);
}
}

PS E:\Code\面试题\大厂面试题第2季\src\main\java\com\atguigu\thread> javac T.java
PS E:\Code\面试题\大厂面试题第2季\src\main\java\com\atguigu\thread> ls
-a---- 2024/1/11 15:07 652 T.class
-a---- 2024/1/11 14:47 235 T.java

PS E:\Code\面试题\大厂面试题第2季\src\main\java\com\atguigu\thread> cd ../../../
PS E:\Code\面试题\大厂面试题第2季\src\main\java> java com.atguigu.thread.T
retValue = 300

# 或者使用 -cp . 参数来运行
PS E:\Code\面试题\大厂面试题第2季\src\main\java> java -cp . com.atguigu.thread.T
retValue = 300

参考: https://www.liaoxuefeng.com/wiki/1252599548343744/1260466914339296

运行java命令的同时打印参数, 可以观察到 , 可以通过自己手动指定参数将参数的默认值进行修改

1
2
3
4
5
6
7
8
9
PS E:\Code\面试题\大厂面试题第2季\src\main\java> java -XX:+PrintFlagsFinal com.atguigu.thread.T
uintx MetaspaceSize = 21807104 {pd product}
retValue = 300



PS E:\Code\面试题\大厂面试题第2季\src\main\java> java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m com.atguigu.thread.T
uintx MetaspaceSize := 536870912 {pd product}
retValue = 300

(4) -XX:+PrintCommandLineFlags
打印命令行参数

1
2
3
4
5
6
$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=265625408 -XX:MaxHeapSize=4250006528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

3.你平时工作用过的JVM常用基本配置参数有哪些?

1.基础知识复习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.thread;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
/*
System.out.println("HelloGC");
Thread.sleep(Integer.MAX_VALUE);
*/

long totalMemory = Runtime.getRuntime().totalMemory(); // 返回Java 虚拟机中的内存总量
long maxMemory = Runtime.getRuntime().maxMemory(); // 返回 Java 虚拟机试图使用的最大内存量
System.out.println("TOTAL_MEMORY(-Xms) = " + totalMemory + "(字节)、" + (totalMemory / (double) 1024 / 1024) + "MB");
System.out.println("MAX_MEMORY(-Xmx) = " + maxMemory + "(字节)、" + (maxMemory / (double) 1024 / 1024) + "MB");
}
}

2.常用参数
-Xms:

初始大小内存, 默认为物理内存1/64
等价于-XX:InitialHeapSize

-Xmx

初始大小内存, 默认为物理内存1/4
等价于-XX:MaxHeapSize

-Xss

设置单个线程栈的大小, 一般默认为512k~1024k
等价于-XX:ThreadStackSize

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
37
38
39
40
$ cat .\HelloGC.java
package com.atguigu.thread;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("HelloGC");
Thread.sleep(Integer.MAX_VALUE);
}
}

$ javac -encoding UTF-8 .\HelloGC.java
$ cd ../../..
$ java com.atguigu.thread.HelloGC
HelloGC
### 此时睡眠了

### 新开一个终端 terminal
$ jps -l
1376 org.jetbrains.jps.cmdline.Launcher
34612
9444 com.atguigu.thread.HelloGC
14408 org.codehaus.classworlds.Launcher
33576 sun.tools.jps.Jps

$ jinfo -flag ThreadStackSize 9444
-XX:ThreadStackSize=0

### 回到第一个terminal
$ java -Xss128k com.atguigu.thread.HelloGC
HelloGC

### 回到第二个terminal
$ jps -l
1376 org.jetbrains.jps.cmdline.Launcher
34612
14408 org.codehaus.classworlds.Launcher
24744 sun.tools.jps.Jps
28616 com.atguigu.thread.HelloGC
$ jinfo -flag ThreadStackSize 28616
-XX:ThreadStackSize=128

-XX:ThreadStackSize=0 并不是说明栈空间大小是0, 而是表明使用平台默认值

-Xmn

-Xmn: 设置年轻代大小

-XX:MetaspaceSize

设置元空间大小: 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
不过元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制
修改元空间大小:

1
-Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:PrintFlagsFinal

添加参数 -XX:+PrintFlagsFinal 查看

1
uintx MetaspaceSize                             = 21807104                            {pd product}

jdk中默认的元空间的大小是 21807104
换算 21807104 / 1024 / 1024 = 20.796875 ≈ 21 M

典型设置案例

添加参数查看 -XX:+PrintCommandLineFlags

1
2
-XX:InitialHeapSize=265625408 -XX:MaxHeapSize=4250006528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 

修改参数

1
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC

运行得到

1
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=4294967296 -XX:MetaspaceSize=536870912 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:ThreadStackSize=1024 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 

-XX:+PrintGCDetails

输出详细GC收集日志信息: -XX:+PrintGCDetails

1
2
3
4
5
6
7
8
9
package com.atguigu.thread;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("HelloGC");
byte[] bytes = new byte[50 * 1024 * 1024];
}
}

vm Options中添加参数

1
-XX:+PrintGCDetails

运行结果

1
2
3
4
5
6
7
8
9
10
HelloGC
Heap
PSYoungGen total 75776K, used 57708K [0x000000076b900000, 0x0000000770d80000, 0x00000007c0000000)
eden space 65024K, 88% used [0x000000076b900000,0x000000076f15b368,0x000000076f880000)
from space 10752K, 0% used [0x0000000770300000,0x0000000770300000,0x0000000770d80000)
to space 10752K, 0% used [0x000000076f880000,0x000000076f880000,0x0000000770300000)
ParOldGen total 173568K, used 0K [0x00000006c2a00000, 0x00000006cd380000, 0x000000076b900000)
object space 173568K, 0% used [0x00000006c2a00000,0x00000006c2a00000,0x00000006cd380000)
Metaspace used 3277K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 359K, capacity 388K, committed 512K, reserved 1048576K

修改参数

1
-Xms10m -Xmx10m -XX:+PrintGCDetails

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HelloGC
[GC (Allocation Failure) [PSYoungGen: 1746K->488K(2560K)] 1746K->696K(9728K), 0.0009593 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 488K->488K(2560K)] 696K->712K(9728K), 0.0005381 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 224K->614K(7168K)] 712K->614K(9728K), [Metaspace: 3257K->3257K(1056768K)], 0.0069167 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 614K->614K(9728K), 0.0003632 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 614K->597K(7168K)] 614K->597K(9728K), [Metaspace: 3257K->3257K(1056768K)], 0.0075417 secs] [Times: user=0.08 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 96K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd18330,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 597K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 8% used [0x00000000ff600000,0x00000000ff6957c0,0x00000000ffd00000)
Metaspace used 3302K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 362K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.atguigu.thread.HelloGC.main(HelloGC.java:6)

GC

FullGC

-XX:SurvivorRatio

设置新生代中eden和S0/S1空间的比例
默认 -XX:SurvivorRatio=8 Eden:S0:S1 = 8:1:1
假如 -XX:SurvivorRatio=4 Eden:S0:S1 = 4:1:1
SurvivorRatio值就是设置eden区的比例占多少, S0/S1相同

代码

1
2
3
4
5
6
7
8
package com.atguigu.thread;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("HelloGC");
}
}

添加参数

1
-XX:+PrintGCDetails -XX:+UseSerialGC -Xms10m -Xmx10m

或者添加参数, 只是省略了 SurvivorRatio 的配置, 默认 SurvivorRatio 就是8

1
2
-XX:+PrintGCDetails -XX:+UseSerialGC -Xms10m -Xmx10m -XX:SurvivorRatio=8

运行结果:
eden:from:to = 2752K:320K:320K ≈ 8:1:1

1
2
3
4
5
6
7
8
9
10
HelloGC
Heap
def new generation total 3072K, used 1774K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 64% used [0x00000000ff600000, 0x00000000ff7bbac0, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
tenured generation total 6848K, used 0K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 0% used [0x00000000ff950000, 0x00000000ff950000, 0x00000000ff950200, 0x0000000100000000)
Metaspace used 3237K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K

修改参数

1
-XX:+PrintGCDetails -XX:+UseSerialGC -Xms10m -Xmx10m -XX:SurvivorRatio=4

运行结果:
eden:from:to = 2368K:512K:512K ≈ 4:1:1

1
2
3
4
5
6
7
8
9
10
HelloGC
Heap
def new generation total 2880K, used 1754K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2368K, 74% used [0x00000000ff600000, 0x00000000ff7b6b48, 0x00000000ff850000)
from space 512K, 0% used [0x00000000ff850000, 0x00000000ff850000, 0x00000000ff8d0000)
to space 512K, 0% used [0x00000000ff8d0000, 0x00000000ff8d0000, 0x00000000ff950000)
tenured generation total 6848K, used 0K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 0% used [0x00000000ff950000, 0x00000000ff950000, 0x00000000ff950200, 0x0000000100000000)
Metaspace used 3235K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K
-XX:NewRatio

配置年轻代与老年代在堆结构的占比
默认
-XX:NewRatio=2新生代占1, 老年代2, 年轻代占整个堆的1/3
假如
-XX:NewRatio=4新生代占1, 老年代4, 年轻代占整个堆的1/5
NewRatio值就是设置老年代的占比,剩下的1给新生代

参数:

1
2
3
4
### 默认
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2
### 修改
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=4

运行结果:
修改前:
新生代: 老年代 = 3072K : 6848K ≈ 1 : 2
修改后:
新生代: 老年代 = 1856K : 8192K ≈ 1 : 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HelloGC
Heap
def new generation total 3072K, used 1778K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 64% used [0x00000000ff600000, 0x00000000ff7bca40, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
tenured generation total 6848K, used 0K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 0% used [0x00000000ff950000, 0x00000000ff950000, 0x00000000ff950200, 0x0000000100000000)
Metaspace used 3242K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K


HelloGC
[GC (Allocation Failure) [DefNew: 1664K->191K(1856K), 0.0018514 secs] 1664K->611K(10048K), 0.0018916 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 1856K, used 231K [0x00000000ff600000, 0x00000000ff800000, 0x00000000ff800000)
eden space 1664K, 2% used [0x00000000ff600000, 0x00000000ff609fe0, 0x00000000ff7a0000)
from space 192K, 99% used [0x00000000ff7d0000, 0x00000000ff7ffff8, 0x00000000ff800000)
to space 192K, 0% used [0x00000000ff7a0000, 0x00000000ff7a0000, 0x00000000ff7d0000)
tenured generation total 8192K, used 419K [0x00000000ff800000, 0x0000000100000000, 0x0000000100000000)
the space 8192K, 5% used [0x00000000ff800000, 0x00000000ff868d80, 0x00000000ff868e00, 0x0000000100000000)
Metaspace used 3215K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 352K, capacity 388K, committed 512K, reserved 1048576K
-XX:MaxTenuringThreshold

设置垃圾最大年龄

1
2
3
4
5
6
7
8
package com.atguigu.thread;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("HelloGC");
Thread.sleep(Integer.MAX_VALUE);
}
}

老年代年龄默认是15
-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

添加参数

1
-XX:MaxTenuringThreshold=12

值得注意的是 MaxTenuringThreshold 的取值范围是: 0~15

1
2
3
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
MaxTenuringThreshold of 20 is invalid; must be between 0 and 15

运行结果:

1
2
3
4
5
6
7
8
9
10
11
$ jps -l                                 
27156 org.jetbrains.jps.cmdline.Launcher
34612
14408 org.codehaus.classworlds.Launcher
7784 sun.tools.jps.Jps
36412 com.atguigu.thread.HelloGC
$ jinfo -flag MaxTenuringThreshold 36412
-XX:MaxTenuringThreshold=12
$ jinfo -flag PrintGCDetails 36412
-XX:-PrintGCDetails

4.强引用、软引用、弱引用、虚引用分别是什么?

整体架构:

强引用(默认支持模式)

obj2 强引用, 不会被垃圾回收

1
2
3
4
5
6
7
8
9
10
11
12
package com.atguigu.jvm;

public class StrongReferenceDemo {
public static void main(String[] args) {
Object obj1 = new Object(); // 这样定义的默认就是强引用
Object obj2 = obj1; // obj2 引用赋值
obj1 = null; // 置空
System.gc();
System.out.println(obj2); // obj2 强引用, 不会被垃圾回收 java.lang.Object@1540e19d
}
}

软引用 SoftReference

当系统内存充足时它不会被回收
当系统内存不足时它会被回收

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.atguigu.jvm;

import java.lang.ref.SoftReference;

public class SoftReferenceDemo {

/**
* 内存够用的时候就保留, 不够用就回收
*/
public static void softRef_Memory_Enough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1); // java.lang.Object@1540e19d
System.out.println(softReference.get()); // java.lang.Object@1540e19d

o1 = null;
System.gc();

System.out.println(o1); // null
System.out.println(softReference.get()); // java.lang.Object@1540e19d
}

/**
* JVM 配置, 故意产生大对象并配置小的内存, 让它内存不够用了导致OOM, 看软引用的回收情况
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRef_Memory_NotEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1); // java.lang.Object@1540e19d
System.out.println(softReference.get()); // java.lang.Object@1540e19d

o1 = null;

try {
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
// Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
throw new RuntimeException(e);
} finally {
System.out.println(o1); // null
System.out.println(softReference.get()); // null
}
}

public static void main(String[] args) {
// softRef_Memory_Enough();

softRef_Memory_NotEnough();
}
}

弱引用 WeakReference
case

不管内存够不够用, 最终都会回收该对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.atguigu.jvm;

import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1); // java.lang.Object@1540e19d
System.out.println(weakReference.get()); // java.lang.Object@1540e19d

o1 = null;
System.gc();

System.out.println(o1); // null
System.out.println(weakReference.get()); // null
}
}

软引用和弱引用的适用场景

你知道弱引用的话, 能谈谈WeakHashMap吗?

WeakHashMap继承AbstractMap,实现了Map接口。和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是”弱键”。在WeakHashMap中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
WeakHashMap内部是通过弱引用来管理entry的,弱引用的特性对应到WeakHashMap上意味着什么呢?将一对key, value放入到WeakHashMap里并不能避免该key值被GC回收,除非在WeakHashMap之外还有对该key的强引用。
和HashMap一样,WeakHashMap是不同步的。可以使用Collections.synchronizedMap方法来构造同步的WeakHashMap。
参考: https://blog.csdn.net/u014294681/article/details/86522487

在WeakHashMap中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除

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
37
38
39
40
41
42
43
44
package com.atguigu.jvm;

import java.util.HashMap;
import java.util.WeakHashMap;

public class WeakHashDemo {
public static void main(String[] args) {
myHashMap();
System.out.println();
myWeakHashMap();
}

private static void myHashMap() {
HashMap<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";

map.put(key, value);
System.out.println(map); // {1=HashMap}

key = null;
System.out.println(map); // {1=HashMap}

System.gc();
System.out.println(map); // {1=HashMap}
}

private static void myWeakHashMap() {
WeakHashMap<Integer, String> map = new WeakHashMap<>();
Integer key = new Integer(2);
String value = "WeakHashMap";

map.put(key, value);
System.out.println(map); // {2=WeakHashMap}

key = null; // key这个键引用不再使用了, 并且之后垃圾回收了, 所以被自动移除了
System.out.println(map); // {2=WeakHashMap}

System.gc();
System.out.println(map); // {}

}
}

虚引用 PhantomReference
虚引用

引用队列

引用队列case

虚引用在垃圾回收之后, 会被放到引用队列中进行保存

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
37
38
39
package com.atguigu.jvm;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class ReferenceQueueDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);

System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());

System.out.println();

o1 = null;
System.gc();
Thread.sleep(500);

System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());

// 运行结果
/*
java.lang.Object@1540e19d
java.lang.Object@1540e19d
null

null
null
java.lang.ref.WeakReference@677327b6
*/

}
}

虚引用case
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
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.atguigu.jvm;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
* java提供了4种引用美型,在垃圾回收的时候,都有自己各白的特点。
* ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行
*
* 创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列,
* 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
* 这相当于是一种通知机制。
*
* 当关联的引用队列中有数据的时候,意味着引用指向的雄内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后做一些我们自己想做的事情。
*/
public class PhantomReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1, referenceQueue);

System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());

System.out.println();
o1 = null;
System.gc();

System.out.println(o1);
// 虚引用, 顾名思义: 就是形同虚设
// phantomReference的get方法总是返回null
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());

}
}

// 运行结果
/*
java.lang.Object@1540e19d
null
null

null
null
java.lang.ref.PhantomReference@677327b6

*/
GCRoots和四大引用小总结

虚引用主要用来跟踪对象被垃圾回收器回收的活动。 虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
参考: https://juejin.cn/post/6844903665241686029#heading-5

5.请谈谈你对OOM的认识

java.lang.StackOverflowError

没有边界的递归
case

1
2
3
4
5
6
7
8
9
10
11
12
package com.atguigu.jvm;

public class StackOverflowErrorDemo {
public static void main(String[] args) {
stackOverflowError();
}

private static void stackOverflowError() {
stackOverflowError();
}
}

1
2
3
Exception in thread "main" java.lang.StackOverflowError
at com.atguigu.jvm.StackOverflowErrorDemo.stackOverflowError(StackOverflowErrorDemo.java:9)
at com.atguigu.jvm.StackOverflowErrorDemo.stackOverflowError(StackOverflowErrorDemo.java:9)
面试题: java.lang.StackOverflowError属于错误(Error)还是异常(Exception)?

面试题: java.lang.StackOverflowError属于错误(Error)还是异常(Exception)?
答案: 错误

java.lang.OutOfMemoryError: Java heap space
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.atguigu.jvm;

import java.util.Random;

public class JavaHeapSpaceDemo {
public static void main(String[] args) {
/*String str = "atguigu";

while (true) {
str += str + new Random(111111) + new Random(222222);
str.intern();
}*/

// java.lang.OutOfMemoryError: Java heap space

// -Xmx10m -Xms10m
byte[] bytes = new byte[50 * 1024 * 1024];
}
}

java.lang.OutOfMemoryError: GC overhead limit exceeded

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
package com.atguigu.jvm;

import java.util.ArrayList;
import java.util.List;

/**
* JVM参数配置演示
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*
* GC回收时间过长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存
* 连续多次GC都只回收了不到2%的极端情况下才会抛出。假如不抛出 GC overhead limit 会发生什么情况呢?
* 那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行。这样就形成恶性循环,
* CPU使用率一直是100%,而GC却没有任何成果
*/
public class GCOverheadDemo {
public static void main(String[] args) {
int i = 0;
List<String> list = new ArrayList<>();

try {
while (true) {
list.add(String.valueOf(++i).intern());
}
} catch (Throwable e) {
System.out.println("i = " + i);
e.printStackTrace();
throw e;
}
}
}

java.lang.OutOfMemoryError: Direct buffer memory

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
package com.atguigu.jvm;

import java.nio.ByteBuffer;

/**
* 配置参数:
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*
* 故障现象:
* Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
*
* 导致原因:
* 写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/0方式,
* 它可以使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。
* 这样能在一些场景中显者提高性能,因为避免了在Java堆和Native堆中来回复制数据。
*
* ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存,属于GC管辖范固,由于需要拷贝所以速度相对较慢*
*
* ByteBuffer.allocateDirect(capability) 第二种方式是分OS本内存,不属GC管范固,由于不需内存拷贝所以速度相对较快。
*
* 但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收
* 这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本内存就会出OutOfMemoryError,那程序就直接崩溃了.
*/
public class DirectBufferMemoryDemo {
public static void main(String[] args) {
System.out.println("配置的maxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// -XX:MaxDirectMemorySize=5m 我们配置为5MB, 但实际使用6MB, 故意使坏
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}

java.lang.OutOfMemoryError: unable to create new native thread

自己做这个实验的时候, 记得不要用root, 创个普通用户, 这俩用户最大线程数不一样的, root会把linux干崩

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
37
38
[root:/opt/java-code-demo]# ls
UnableCreateNewThreadDemo.java
[root:/opt/java-code-demo]# cat UnableCreateNewThreadDemo.java
package com.atguigu.jvm;

/**
* 高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unable to create new native thread
* 准确的讲该native thread异常与对应的平台有关
*
* 导致原因:
* 1.你的应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载极限
* 2.你的服务器并不允许你的应用程序创建这么多线程, linux系统默认允许单个进程可以创建的线程数是1024个,
* 你的应用创建超过这个数量,就会报java.lang.0utOfMemoryError: unable to create new native thread
*
* 解决办法:
* 1.想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
* 2.对于有的应用,确实需要创建很多线程,远超过linux系统的默以1024个线程的限制,可以通过修改Linux服务器配置,扩大Linux默认限制
*/
public class UnableCreateNewThreadDemo {
public static void main(String[] args) {
for (int i = 1; ; i++) {
System.out.println("i = " + i);

new Thread(() -> {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, String.valueOf(i)).start();
}
}
}

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at com.atguigu.jvm.UnableCreateNewThreadDemo.main(UnableCreateNewThreadDemo.java:27)

杀死进程

1
2
ps -ef | grep java
kill -9 1234

服务器级别调参调优
查看当前用户可创建最大线程数

1
2
3
4
5
6
7
8
9
10
11
12
[roudoukou@xiamu java-code-demo]$ ulimit -u
4096

# 可以发现, 除了root, 其他账户都限制在 4096 个
[roudoukou@xiamu java-code-demo]$ vim /etc/security/limits.d/20-nproc.conf
# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.

* soft nproc 4096
root soft nproc unlimited

java.lang.OutOfMemoryError: Metaspace

使用 java -XX:+PrintFlagsInitial 命令查看本机的初始化参数, -XX:MetaspaceSize为21810376B(大约20.8M)

导入依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.atguigu.jvm;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* JVM参数
* XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
* <p>
* Java 8及之后的版本使用Metaspace来替代永久代。
* Metaspace是方法区在HotSpot中的实现,它与持久代最大的区别在于: Metaspace 并不在虚拟机内存中而是使用本地内存
* 即java8中,classe metadata(the virtual machines internal presentation of Java class),被存储在做
* Metaspace 的 native memory
* 永久代 (java8后被原空问Metaspace取代了)存放了以下信息:
* 虚拟机加载的类信息
* 常量池
* 静态变量
* 即时编译后的代码
* <p>
* 模拟 Metaspace 空问溢出,我们不断生成类往元空问灌,类占据的空问总是会超过 Metaspace 指定的空问大小的
*/
public class MetaspaceOOMTest {

static class OOMTest {
}

public static void main(String[] args) {
int i = 0; // 模拟计数多少次以后发生异常

try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
} catch (Throwable e) {
System.out.println("多少次后发生了异常: " + i);
// throw new RuntimeException(e);
e.printStackTrace();
}
}
}

// 运行结果
/*
多少次后发生了异常: 308
java.lang.OutOfMemoryError: Metaspace
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:467)
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:117)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
at com.atguigu.jvm.MetaspaceOOMTest.main(MetaspaceOOMTest.java:45)
*/

6.GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈

GC算法(引用计数/复制/标清/标整)是内存回收的方法论, 垃圾收集器就是算法落地实现
因为目前为止还没有完美的收集器出现, 更加没有万能的收集器, 只是针对具体应用最合适的收集器, 进行分代收集

4种主要垃圾收集器

1.串行垃圾回收器(Serial)

它为单线程环境设计且只有一个线程进行垃圾回收, 会暂停所有的用户线程, 所以不适合服务器环境

2.并行垃圾回收器(Parallel)

多个垃圾收集线程并行工作, 此时用户线程是暂停的, 适用于科学计算/大数据处理首台处理等弱交互场景

3.并发垃圾回收器(CMS)

用户线程和垃圾收集线程同时执行(不一定是并行, 可能交替执行), 不需要停顿用户线程
互联网公司多用它, 适用对响应时间有要求的场景

4.小总结

5.G1垃圾回收器

G1 垃圾回收器将堆内存分割成不同的区域然后并发的对其垃圾回收

7.怎么查看服务器默认的垃圾收集器是那个?生产上如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?

怎么查看默认的垃圾收集器是哪个?

JVM参数:
java -XX:+PrintCommandLineFlags -version

1
2
3
4
5
6
$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=265625408 -XX:MaxHeapSize=4250006528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

由此可以得知: 默认的垃圾回收器就是: -XX:+UseParallelGC

默认的垃圾收集器有哪些?

图上的这六种, 以往还有一种叫 UseSerialOldGC 已经被废弃了
UseSerialGC,UseParallelGC,UseConcMarkSweepGC,UseParNewGC,UseParallelOldGC,UseG1GC

1
2
3
4
5
6
7
8
package com.atguigu.jvm;

public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("HelloGC");
Thread.sleep(Integer.MAX_VALUE);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ jps
# 啥也没有配置
$ jinfo -flag UseSerialGC 1234
-XX:-UseSerialGC
$ jinfo -flag UseParallelGC 1234
-XX:+UseParallelGC

$ jps
# 配置 `-XX:+UseSerialGC` 之后
$ jinfo -flag UseSerialGC 1234
-XX:+UseSerialGC
$ jinfo -flag UseParallelGC 1234
-XX:-UseParallelGC

垃圾收集器

部分参数预先说明

Server/Client模式分别是什么意思?

新生代

串行GC(Serial)/(Serial Copying)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.atguigu.jvm;

import java.util.Random;

public class GCDemo {
public static void main(String[] args) {
String str = "atguigu";

try {
while (true) {
str += str + new Random(111111) + new Random(222222);
str.intern();
}
} catch (Exception e) {
throw new RuntimeException(e);
}

// java.lang.OutOfMemoryError: Java heap space
}
}

参数:

1
2
# Default + Tenured
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC

结果:

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
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
[GC (Allocation Failure) [DefNew: 2705K->320K(3072K), 0.0024652 secs] 2705K->991K(9920K), 0.0029408 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2193K->228K(3072K), 0.0018383 secs] 2865K->1789K(9920K), 0.0018744 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2103K->0K(3072K), 0.0012495 secs] 3665K->2701K(9920K), 0.0012911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 964K->0K(3072K), 0.0008015 secs] 3666K->3613K(9920K), 0.0008289 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 1876K->0K(3072K), 0.0012697 secs] 5489K->5436K(9920K), 0.0012991 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 1876K->1876K(3072K), 0.0000214 secs][Tenured: 5436K->2757K(6848K), 0.0038461 secs] 7313K->2757K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0041643 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 1877K->1877K(3072K), 0.0000230 secs][Tenured: 6404K->5492K(6848K), 0.0043658 secs] 8281K->5492K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0044620 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 1932K->1932K(3072K), 0.0000228 secs][Tenured: 5492K->2757K(6848K), 0.0041999 secs] 7425K->2757K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0042884 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 1877K->1877K(3072K), 0.0000186 secs][Tenured: 6405K->6114K(6848K), 0.0049234 secs] 8282K->6114K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0050059 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 6114K->6095K(6848K), 0.0054355 secs] 6114K->6095K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0054763 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 3072K, used 109K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61b500, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
tenured generation total 6848K, used 6095K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 89% used [0x00000000ff950000, 0x00000000fff43e58, 0x00000000fff44000, 0x0000000100000000)
Metaspace used 3316K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 362K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at com.atguigu.jvm.GCDemo.main(GCDemo.java:11)

并行GC(ParNew)

代码同上

参数:

1
2
# ParNew + Tenured
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC

结果:

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
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
[GC (Allocation Failure) [ParNew: 2645K->318K(3072K), 0.0008312 secs] 2645K->1007K(9920K), 0.0013505 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2222K->287K(3072K), 0.0009938 secs] 2911K->1668K(9920K), 0.0010280 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2182K->15K(3072K), 0.0007004 secs] 3564K->3032K(9920K), 0.0007369 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1016K->34K(3072K), 0.0010487 secs] 4033K->4874K(9920K), 0.0010808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1910K->30K(3072K), 0.0012064 secs] 6751K->6694K(9920K), 0.0012526 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1906K->1906K(3072K), 0.0000220 secs][Tenured: 6664K->2634K(6848K), 0.0037589 secs] 8571K->2634K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0042152 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1876K->1876K(3072K), 0.0000200 secs][Tenured: 6282K->5370K(6848K), 0.0027142 secs] 8159K->5370K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0028315 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1932K->1932K(3072K), 0.0000162 secs][Tenured: 5370K->2634K(6848K), 0.0023289 secs] 7303K->2634K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0023926 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1877K->1877K(3072K), 0.0000233 secs][Tenured: 6282K->6114K(6848K), 0.0058202 secs] 8159K->6114K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0059035 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [Tenured: 6114K->6095K(6848K), 0.0035700 secs] 6114K->6095K(9920K), [Metaspace: 3284K->3284K(1056768K)], 0.0035997 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
Heap
par new generation total 3072K, used 109K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61b4f0, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
tenured generation total 6848K, used 6095K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 89% used [0x00000000ff950000, 0x00000000fff43e58, 0x00000000fff44000, 0x0000000100000000)
Metaspace used 3316K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 362K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at com.atguigu.jvm.GCDemo.main(GCDemo.java:11)
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release

并行回收GC(Parallel)/(Parallel Scavenge)

参数:

1
2
# PSYoungGen + ParOldGen
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC

结果:

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
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
[GC (Allocation Failure) [PSYoungGen: 2047K->485K(2560K)] 2047K->777K(9728K), 0.0022195 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2483K->483K(2560K)] 2775K->1391K(9728K), 0.0013570 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2360K->288K(2560K)] 3268K->1651K(9728K), 0.0009577 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1728K->400K(2560K)] 4916K->3587K(9728K), 0.0021833 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2263K->304K(2560K)] 7274K->6227K(9728K), 0.0012164 secs] [Times: user=0.01 sys=0.02, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 304K->320K(1536K)] 6227K->6243K(8704K), 0.0015532 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 320K->0K(1536K)] [ParOldGen: 5923K->3385K(7168K)] 6243K->3385K(8704K), [Metaspace: 3284K->3284K(1056768K)], 0.0100409 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 20K->32K(2048K)] 7053K->7065K(9216K), 0.0004494 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 7033K->5202K(7168K)] 7065K->5202K(9216K), [Metaspace: 3284K->3284K(1056768K)], 0.0065360 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 40K->32K(2048K)] 7067K->7058K(9216K), 0.0003320 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 7026K->2466K(7168K)] 7058K->2466K(9216K), [Metaspace: 3284K->3284K(1056768K)], 0.0075694 secs] [Times: user=0.09 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 20K->0K(2048K)] 4310K->4290K(9216K), 0.0004211 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4290K->4290K(9216K), 0.0005201 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4290K->4290K(7168K)] 4290K->4290K(9216K), [Metaspace: 3284K->3284K(1056768K)], 0.0031239 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4290K->4290K(9216K), 0.0005009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4290K->4271K(7168K)] 4290K->4271K(9216K), [Metaspace: 3284K->3284K(1056768K)], 0.0119991 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2048K, used 61K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 5% used [0x00000000ffd00000,0x00000000ffd0f428,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 7168K, used 4271K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 59% used [0x00000000ff600000,0x00000000ffa2be20,0x00000000ffd00000)
Metaspace used 3316K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 362K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.atguigu.jvm.GCDemo.main(GCDemo.java:11)

老年代

串行GC(Serial Old)/(Serial MSC)

参数:

1
2
3
# 理论知道即可, 实际中已经被优化掉了, 没有了
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC

并行GC(Parallel Old)/(Parallel MSC)

参数:

1
2
3
4
# PSYoungGen + ParOldGen
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelOldGC
# 不加就是默认 UseParallelGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags

并发标记清除GC(CMS)

4步过程:
1.初始化(CMS initial mark)

2.并发标记(CMS concurrent mark)和用户线程一起

3.重新标记(CMS remark)

4.并发清除(CMS concurrent sweep)和用户线程一起

四步概述:

优缺点:
1.优点:
并发收集低停顿
2.缺点:
并发执行, 对CPU资源压力大

采用的标记清除算法会导致大量碎片

参数:

1
2
3
# par new generation + concurrent
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC

垃圾收集器配置代码总结

底层代码:

如何选择垃圾收集器

8.G1垃圾收集器

参数:

1
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC

运行:

以前收集器特点

年轻代和老年代是各自独立且连续的内存块;
年轻代收集使用单eden+SO+S1进行复制算法;
老年代收集必须扫描整个老年代区域;
都是以尽可能少而快速地执行GC为设计原则

G1是什么?



特点

底层原理
Region区域化垃圾收集器

最大好处是化整为零, 避免全内存扫描, 只需要按照区域来进行扫描即可

回收步骤

4步过程

case案例

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.atguigu.jvm;

import java.util.Random;

public class GCDemo {
public static void main(String[] args) {
String str = "atguigu";

try {
while (true) {
str += str + new Random(111111) + new Random(222222);
str.intern();
}
} catch (Exception e) {
throw new RuntimeException(e);
}

// java.lang.OutOfMemoryError: Java heap space
}
}

参数

1
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC 

常用配置参数(了解)

和CMS相比的优势

小总结

JVMGC结合SpringBoot微服务优化

1.使用 mvn clean package 打包
2.在有包的路径下, 运行jar命令, 公式如下:

1
java -server jvm的各种参数 -jar

原先

1
java -jar springboot.jar

使用参数调优后

1
2
java -server -Xms1024m -Xmx1024m -XX:+UseG1GC -jar springboot.jar

9.生产环境服务器变慢,诊断思路和性能评估谈谈?

1.整机: top
1
2
3
$ top
$ uptime

2.CPU: vmstat

参考: https://www.cnblogs.com/ggjucheng/archive/2012/01/05/2312625.html

1
2
3
4
5
6
7
# 间隔两秒, 采样3次
[01:29:01] root :: xiamu ➜ ~ » vmstat -n 2 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 993336 2156 1079908 0 0 7 12 106 171 0 0 100 0 0
0 0 0 993212 2156 1079908 0 0 0 0 387 649 0 0 100 0 0
1 0 0 993212 2156 1079908 0 0 0 0 391 645 0 0 100 0 0

拓展的命令
查看额外:
查看所有cpu核信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[01:29:24] root :: xiamu  ➜  ~ » mpstat -P ALL 2
Linux 3.10.0-1160.el7.x86_64 (xiamu) 2024年01月13日 _x86_64_ (4 CPU)

01时35分19秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
01时35分21秒 all 0.13 0.00 0.13 0.00 0.00 0.00 0.00 0.00 0.00 99.75
01时35分21秒 0 0.00 0.00 0.50 0.00 0.00 0.00 0.00 0.00 0.00 99.50
01时35分21秒 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
01时35分21秒 2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
01时35分21秒 3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00

01时35分21秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
01时35分23秒 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
01时35分23秒 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
01时35分23秒 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
01时35分23秒 2 0.00 0.00 0.50 0.00 0.00 0.00 0.00 0.00 0.00 99.50
01时35分23秒 3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00

每个进程使用cpu的用量分解信息

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
# 编写死循环
[01:47:37] root :: xiamu ➜ /opt/java-code-demo » cat Dead.java 1 ↵
import java.util.*;

public class Dead {
public static void main(String[] args) {
while (true) {
System.out.println(new Random().nextInt(10000));
}
}
}

# 编译运行
[01:47:40] root :: xiamu ➜ /opt/java-code-demo » javac Dead.java
[01:47:40] root :: xiamu ➜ /opt/java-code-demo » java Dead

# 新开一个终端
λ ~/ ps -ef | grep java
root 7498 7451 1 1月12 ? 00:09:21 /usr/lib/jvm/java-1.8.0-openjdk/bin/java -Xms512m -Xmx512m -Xmn256m -Dnacos.standalone=true -Djava.ext.dirs=/usr/lib/jvm/java-1.8.0-openjdk/jre/lib/ext:/usr/lib/jvm/java-1.8.0-openjdk/lib/ext:/home/nacos/plugins/health:/home/nacos/plugins/cmdb:/home/nacos/plugins/mysql -Xloggc:/home/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dnacos.home=/home/nacos -jar /home/nacos/target/nacos-server.jar --spring.config.location=classpath:/,classpath:/config/,file:./,file:./config/,file:/home/nacos/conf/,/home/nacos/init.d/ --spring.config.name=application,custom --logging.config=/home/nacos/conf/nacos-logback.xml --server.max-http-header-size=524288
root 42360 7280 23 01:48 pts/1 00:00:07 java Dead
root 42484 42402 0 01:49 pts/0 00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox java
λ ~/ pidstat -u 1 -p 42360
Linux 3.10.0-1160.el7.x86_64 (xiamu) 2024年01月13日 _x86_64_ (4 CPU)

01时49分22秒 UID PID %usr %system %guest %CPU CPU Command
01时49分23秒 0 42360 10.00 12.00 0.00 22.00 0 java
01时49分24秒 0 42360 9.00 14.00 0.00 23.00 0 java
01时49分25秒 0 42360 9.00 11.00 0.00 20.00 0 java
01时49分26秒 0 42360 8.00 14.00 0.00 22.00 0 java
01时49分27秒 0 42360 9.00 13.00 0.00 22.00 0 java
01时49分28秒 0 42360 9.00 12.00 0.00 21.00 0 java
01时49分29秒 0 42360 8.00 15.00 0.00 23.00 0 java
01时49分30秒 0 42360 9.00 13.00 0.00 22.00 0 java
01时49分31秒 0 42360 8.00 14.00 0.00 22.00 0 java
01时49分32秒 0 42360 8.91 13.86 0.00 22.77 0 java
3.内存: free

-m参数会四舍五入, 约等于4G, 只显示了3G

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
λ ~/ free
total used free shared buff/cache available
Mem: 3861300 1828680 928556 15040 1104064 1728592
Swap: 2097148 0 2097148
λ ~/ free -h
total used free shared buff/cache available
Mem: 3.7G 1.7G 907M 14M 1.1G 1.6G
Swap: 2.0G 0B 2.0G
λ ~/ free -g
total used free shared buff/cache available
Mem: 3 1 0 0 1 1
Swap: 1 0 1
λ ~/ free -m
total used free shared buff/cache available
Mem: 3770 1786 906 14 1078 1687
Swap: 2047 0 2047


λ ~/ pidstat -p 42360 -r 2
Linux 3.10.0-1160.el7.x86_64 (xiamu) 2024年01月13日 _x86_64_ (4 CPU)

01时52分42秒 UID PID minflt/s majflt/s VSZ RSS %MEM Command
01时52分44秒 0 42360 1.00 0.00 3404056 49504 1.28 java
01时52分46秒 0 42360 0.50 0.00 3404056 49504 1.28 java
01时52分48秒 0 42360 0.50 0.00 3404056 49504 1.28 java
4.硬盘: df
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
root@xiamu [11时32分31秒] [~]
-> # df
文件系统 1K-块 已用 可用 已用% 挂载点
devtmpfs 1913584 0 1913584 0% /dev
tmpfs 1930648 0 1930648 0% /dev/shm
tmpfs 1930648 12804 1917844 1% /run
tmpfs 1930648 0 1930648 0% /sys/fs/cgroup
/dev/mapper/centos_roudoukou-root 38778880 16090904 22687976 42% /
/dev/sda1 1038336 189220 849116 19% /boot
tmpfs 386132 12 386120 1% /run/user/42
overlay 38778880 16090904 22687976 42% /var/lib/docker/overlay2/dc9d56c5fc9be6fa674ac3082e0bc4e11e752d3146268351eae5fa85cf8b54e4/merged
overlay 38778880 16090904 22687976 42% /var/lib/docker/overlay2/e06aef924ef732cac3faa12ef3282d0033af42af84354a02ce636cf7057eed2b/merged
overlay 38778880 16090904 22687976 42% /var/lib/docker/overlay2/f334a6550d72481f28ea577223e18da53e6c62a9580a6f2ca6bcad9540b3bfb5/merged
tmpfs 386132 0 386132 0% /run/user/0
root@xiamu [11时32分48秒] [~]
-> # df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 1.9G 13M 1.9G 1% /run
tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
/dev/mapper/centos_roudoukou-root 37G 16G 22G 42% /
/dev/sda1 1014M 185M 830M 19% /boot
tmpfs 378M 12K 378M 1% /run/user/42
overlay 37G 16G 22G 42% /var/lib/docker/overlay2/dc9d56c5fc9be6fa674ac3082e0bc4e11e752d3146268351eae5fa85cf8b54e4/merged
overlay 37G 16G 22G 42% /var/lib/docker/overlay2/e06aef924ef732cac3faa12ef3282d0033af42af84354a02ce636cf7057eed2b/merged
overlay 37G 16G 22G 42% /var/lib/docker/overlay2/f334a6550d72481f28ea577223e18da53e6c62a9580a6f2ca6bcad9540b3bfb5/merged
tmpfs 378M 0 378M 0% /run/user/0
5.磁盘IO: iostat

磁盘IO性能评估

1
iostat -xdk 2 3

查看额外 pidstat -d 采样间隔秒数 -p 进程号

6.网络IO: ifstat

默认本地没有,

查看网络IO
ifstat

10.假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位

定位到具体线程或代码: (ps -mp 进程 -o THREAD,tid,time)
参数解释:
-m显示所有的线程
-p pid 进程使用cpu时间
-o 该参数后是用户自定义格式

printf “%x\n” 有问题的线程ID

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[root@xiamu ~ ]$ jps -l
3136 -- process information unavailable
88579 Dead
88750 sun.tools.jps.Jps
[root@xiamu ~ ]$ ps -ef | grep java | grep -v grep
root 7498 7451 1 01:20 ? 00:16:46 /usr/lib/jvm/java-1.8.0-openjdk/bin/java -Xms512m -Xmx512m -Xmn256m -Dnacos.standalone=true -Djava.ext.dirs=/usr/lib/jvm/java-1.8.0-openjdk/jre/lib/ext:/usr/lib/jvm/java-1.8.0-openjdk/lib/ext:/home/nacos/plugins/health:/home/nacos/plugins/cmdb:/home/nacos/plugins/mysql -Xloggc:/home/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dnacos.home=/home/nacos -jar /home/nacos/target/nacos-server.jar --spring.config.location=classpath:/,classpath:/config/,file:./,file:./config/,file:/home/nacos/conf/,/home/nacos/init.d/ --spring.config.name=application,custom --logging.config=/home/nacos/conf/nacos-logback.xml --server.max-http-header-size=524288
root 88579 88378 75 23:52 pts/1 00:01:16 java Dead
[root@xiamu ~ ]$ ps -mp 88579 -o THREAD,tid,time
USER %CPU PRI SCNT WCHAN USER SYSTEM TID TIME
root 75.2 - - - - - - 00:02:06
root 0.0 19 - futex_ - - 88579 00:00:00
root 73.5 19 - - - - 88580 00:02:03
root 0.2 19 - futex_ - - 88581 00:00:00
root 0.3 19 - futex_ - - 88582 00:00:00
root 0.3 19 - futex_ - - 88583 00:00:00
root 0.2 19 - futex_ - - 88584 00:00:00
root 0.1 19 - futex_ - - 88585 00:00:00
root 0.0 19 - futex_ - - 88586 00:00:00
root 0.0 19 - futex_ - - 88587 00:00:00
root 0.0 19 - futex_ - - 88588 00:00:00
root 0.0 19 - futex_ - - 88589 00:00:00
root 0.0 19 - futex_ - - 88590 00:00:00
root 0.0 19 - futex_ - - 88591 00:00:00
root 0.0 19 - futex_ - - 88592 00:00:00
root 0.1 19 - futex_ - - 88593 00:00:00
# 从当前位置找到CPU占用最大的TID 线程ID
# 转为16进制小写
[root@xiamu ~ ]$ printf "%x\n" 88580
15a04
[root@xiamu ~ ]$ jstack 88579 | grep 15a04 -A60
"main" #1 prio=5 os_prio=0 tid=0x00007f7414009800 nid=0x15a04 runnable [0x00007f741cb61000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x00000000c503cb70> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x00000000c5005378> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x00000000c5005330> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
- eliminated <0x00000000c5005378> (a java.io.PrintStream)
at java.io.PrintStream.print(PrintStream.java:597)
at java.io.PrintStream.println(PrintStream.java:736)
- locked <0x00000000c5005378> (a java.io.PrintStream)
at Dead.main(Dead.java:6)

"VM Thread" os_prio=0 tid=0x00007f7414077800 nid=0x15a09 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f741401e800 nid=0x15a05 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f7414020800 nid=0x15a06 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f7414022000 nid=0x15a07 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f7414024000 nid=0x15a08 runnable

"VM Periodic Task Thread" os_prio=0 tid=0x00007f74140e5800 nid=0x15a11 waiting on condition

JNI global references: 5

由此可以定位到具体代码: at Dead.main(Dead.java:6)

11.对于JDK自带的JVM监控和性能分析工具用过哪些? 一般你是怎么用的?

github骚操作

常用词含义

in关键词限制搜索范围

stars或fork数量关键词去查找

awesome加强搜索

高亮显示某一行代码

项目内搜索

https://docs.github.com/en/get-started/accessibility/keyboard-shortcuts

搜索某个地区内的大佬

栈管运行, 堆管存储
理论(技术case + 生活case) 代码 小总结
线程操纵资源类, 判断干活唤醒通知, 严防多线程并发状态下的虚假唤醒
物以类聚人以群分
任何人在他牛逼之前定会有段苦逼的岁月但请你像傻逼一样的坚持终于会有一个像宋红康一样装B的结果
show me you code

天上飞的理念, 必有落地的实现


面试题第二季
https://xiamu.icu/Java/面试题第二季/
作者
肉豆蔻吖
发布于
2024年1月8日
许可协议