JVM之GC浅析
Wirte by 021.
对象头
堆特性
- Eden
- 存活对象少
- S1
- S2
- Old
- 存活对象多
对象存活晋升过程
- 按年龄
1 | 对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。 |
- 动态年龄分配函数
1 | uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { |
什么是垃圾?(对象引用详解)
什么是强引用Strong Reference?
1
Object o = new Object();
特点
1
只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。
什么情况下会被回收?
1
2Object o = new Object();
o = null;
什么是软引用SoftReference?
特点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());
Student student = studentSoftReference.get();
System.out.println(student);
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
//// 将内存调小 -Xmx20M ,输出结果:
[B@11d7fff
[B@11d7fff
null什么时候会被回收?
1
当JVM内存不足,执行GC后,内存依然不足,会回收软引用.
什么是弱引用WeakReference?
特点
1
2
3
4
5
6
7
8
9WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
//输出结果:
[B@11d7fff
null什么情况下会被回收?
1
只要触发GC就会被回收.
什么是虚引用PhantomReference?
特点
1
NIO中,就运用了虚引用管理堆外内存,用于堆外内存的释放.
看官方源码注释 : 本大神硬核翻译!希望你能懂
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
53public class PhantomReference<T> extends Reference<T>
/**
* Phantom reference objects, which are enqueued after the collector
* determines that their referents may otherwise be reclaimed. Phantom
* references are most often used for scheduling pre-mortem cleanup actions in
* a more flexible way than is possible with the Java finalization mechanism.
*
虚引用对象,在垃圾收集器决定这些引用可能被回收时入队,
虚引用经常用于在“对象死亡后,对对象进行死亡讣告”
是一种可能比Java直接回收的更灵活一种方式.
* <p> If the garbage collector determines at a certain point in time that the
* referent of a phantom reference is <a
* href="package-summary.html#reachability">phantom reachable</a>, then at that
* time or at some later time it will enqueue the reference.
*
//此段没有太大的意义
* <p> In order to ensure that a reclaimable object remains so, the referent of
* a phantom reference may not be retrieved: The <code>get</code> method of a
* phantom reference always returns <code>null</code>.
*
// 为了保证 已确定回收对象 的规则, 虚引用不再返回该对象的引用.
* <p> Unlike soft and weak references, phantom references are not
* automatically cleared by the garbage collector as they are enqueued. An
* object that is reachable via phantom references will remain so until all
* such references are cleared or themselves become unreachable.
*
* @author Mark Reinhold
* @since 1.2
*/
//不像其他引用一样,在虚引用入队后不会自动被垃圾回收器清理,
一个可达对象被虚引用所引用后会一直保持在列队,直到这些引用被清理或者这些引用变得不可达.
=================================
/**
* Reference queues, to which registered reference objects are appended by the
* garbage collector after the appropriate reachability changes are detected.
*
* @author Mark Reinhold
* @since 1.2
*/
当垃圾回收器在适合的时机检测到可达性发生改变时决定 将这些 引用 加入列队.
public class ReferenceQueue<T> {
什么情况下会被回收?
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
118class A{
@Override
protected void finalize() throws Throwable {
System.out.println("A对象 被回收了");
}
protected void freeMem() throws Throwable {
System.out.println("执行堆外内存释放");
}
}
class B{
@Override
protected void finalize() throws Throwable {
System.out.println("B对象 被回收了");
}
}
public class PerformanceTest {
final int a = 0;
final int b =10;
final static int c = 0;
volatile int d = 0;
volatile int e = 0;
public static void main(String[] args) throws InterruptedException {
PerformanceTest p = new PerformanceTest();
//p.test(0,0);
//打印当前对象内存分布
//System.out.println(VM.current().details());
//System.out.println(ClassLayout.parseClass(PerformanceTest.class).toPrintable());
//虚引用测试
p.phantomReferenceTest();
}
private void phantomReferenceTest() throws InterruptedException {
ReferenceQueue queue = new ReferenceQueue();
List<byte[]> bytes = new ArrayList<>();
//强引用
A a = new A();
B b = new B();
//虚引用
PhantomReference<A> reference = new PhantomReference<A>(new A(),queue);
//弱引用
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
System.out.println("强引用GC回收前强引用====================="+a);
System.out.println("强引用GC回收前弱引用:====================="+weakReference.get());
//此线程触发GC 设置-Xmx20M
new Thread(() -> {
for (int i = 0; i < 30;i++ ) {
bytes.add(new byte[1024 * 1024]);
}
}).start();
Thread.sleep(1000);
System.out.println("强引用GC回收后弱引用:====================="+weakReference.get());
new Thread(() -> {
while (true) {
Reference poll = queue.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
if (poll == null) {
System.out.println("内存中没有引虚拟引用了,被清理了");
try {
new A().freeMem();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return;
}
}
}).start();
System.out.println("强引用GC回收后强引用A:====================="+a);
}
}
//输出结果: 执行结果有乱序,不影响,用序号标记解释
1.强引用GC回收前强引用=====================com.mybrainsbox.performance.A@621be5d1
2.强引用GC回收前弱引用:=====================[B@573fd745
3.A对象 被回收了
4.强引用GC回收后弱引用:=====================null
5.强引用GC回收后强引用A:=====================com.mybrainsbox.performance.A@621be5d1
6.虚引用被回收了:java.lang.ref.PhantomReference@6d39548b
7.内存中没有引虚拟引用了,被清理了
8.B对象 被回收了
9.执行堆外内存释放
10.A对象 被回收了
====================================================
3.A对象 被回收了 这一步输出对应 :
PhantomReference<A> reference = new PhantomReference<A>(new A(),queue);
一旦触发GC, 被PhantomReference引用的对象都会被设置为弱引用,并加入虚引用列队.等待回收.
所以这一步在GC触发时输出.
6.虚引用被回收了:java.lang.ref.PhantomReference@6d39548b
这一步是 虚引用列队在GC后拿到被虚引用引用的new A()最后的回执,所有的可达性检测决定可能要回收的对象都会被加入此列队,直到poll()出全部.
7.内存中没有引虚拟引用了,被清理了;
这一步是poll出了所有的虚引用的对象列队;
8.B对象 被回收了
10.A对象 被回收了
8和10是方法出栈被回收的.
怎么样找到垃圾?
引用计数器
1
在对象头里,有数据位表示该对象有没有引用,当引用计数为0对象将被回收.
- 缺陷 :循环引用 容易引起孤岛效应—- 即 : A -> B -> C , 3个对象互相引用,计数不为0,但实际已经没有任何引用指向这三个对象.
- Root Searching 根可达算法
- 根路径
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)的引用的对象。
- 根路径
常见GC垃圾回收算法
标记 -> 清除
特点
1
2
3
4
51,标记过程:
从GC root出发遍历所有对象,在可达对象头中的markeword进行标记.
2,清理过程:遍历堆中对象,判断对象头markword是否存活标记,进行回收.
缺陷
效率问题:需要2遍扫描,标记和清除都需要遍历,效率不高;
空间问题:标记清除后会产生大量不连续的内存水平,空间碎片太多会导致大内存对象无法生成而频繁进行 GC。
适用堆区
- Old
触发GC类型
- Major GC/老年代GC
标记 -> 复制
特点
1
2
3
4
51,标记 : 将年轻代的空间一分为三,[Eden],[ s1 ] ,[ s2 ],比例是:8:1:1 ,
2,复制: 当使用 s1内存块 使用率达到阈值,s2空白,将存活对象复制到s2内存空间.
3,清理 : 清理s1空间块. 通过这个方式避免了碎片化空间问题.缺陷
- 效率问题:标记和清除都需要遍历,效率不高;
- 空间问题:内存空间浪费,最大10%,
适用堆区
- Eden
触发GC类型
- MinorGC/年轻代GC
标记 -> 压缩(整理)
特点
1
2
3
41,先标记
2,压缩整理: 把可达对象往内存块偏移量折中的任意一端移动,达到空间整洁规整。
缺陷
- 效率问题:需要扫描2次,移动对象引用需要重新引用地址,,效率不如复制算法高效;
适用堆区
- Old
- 触发GC类型
- Major GC/老年代GC
GC垃圾回收器(算法的实现)
CMS
STW安全点
- 编译器提前编译好安全区域标志位,通过对象oopmap映射,GC通过opmap表来判断线程是否到达对象安全点.
1 | HotSpot 用的就是映射表,这个表叫 OopMap。 |
什么时候触发GC
- l