梦想还是要有的,万一忘了咋办?

0%

依赖数据

  • 运行日志
  • 异常堆栈
  • GC日志
  • 线程快照
  • 堆转储快照

获取数据

工具概述

工具存放位置

${JavaHome}/bin/

工具代码逻辑在

jdk/lib/tools.jar

Jdk1.5虚拟机时
   
   启动时增加 -Dcom.sun.management.jmxremote 启动jmx功能,否则部分工具无法使用

工具介绍

upload successful

jps

虚拟机进程状况恐惧,类似linux ps指令。用于获取当前运行的 java进程情况

jps命令格式:

jps [options] [hostid]

jps执行样例:

$ ~ jps -l
43629 sun.tools.jps.Jps
431

jps主要选项
upload successful

jstat

虚拟机统计信息监控工具jstat(Jvm Statistics Monitoring Tool),可以获取虚拟机进程中:

  • 类装载
  • 内存
  • 垃圾回收
  • JIT编译

jstat命令格式:

jstat [option vmid [interval[s|ms] [count] ]
   
远程vmid格式

[protocol:][//]lvmid[@hostname[:port]/servername]

其中

  • interval 间隔时间,默认 无
  • count 执行次数,默认1次

示例:

jstat -gc 2764 100 10
   
查询进程为2764的虚拟机 垃圾收集信息,每个100ms查询一次,一共查询10次。

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
~ jstat -gc 431  100 10
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525
14144.0 14144.0 0.0 0.0 113536.0 2372.7 283440.0 156408.7 378640.0 349994.2 48716.0 42774.9 411 5.382 28 80.143 85.525

其中

YGC      YGCT          FGC    FGCT     GCT

YGC次数   YGC总消耗时间  FGc次数 Fgc总时间 所有GC总时间
jstat命令选项:
jstat

jinfo

Java配置信息工具jinfo(Configuration Info for java)作用:

  • 实时查看、调整 虚拟机各项参数

jinfo命令格式:

jinfo [option] pid

Mac + Jdk1.8 无法使用测试jinfo命令。

jmap

jmap(Memory Map for Java)用于生成堆转储快照(一般为heapdump或dump文件)。不用jmap还可以通过以下方式获取dump文件:

  • -XX:+HeapDumpOnOutOfMemoryError
    当虚拟机遇到oom异常时就会自动生成dump文件。
  • -XX:+HeapDumpOnCtrlBreak
    可以使用ctrl+break键让虚拟机生成dump文件
  • linux 下通过kill -3 pid
    吓唬虚拟机生成dump文件。

jmap工具主要选项

jmap

dump文件内容

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
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):

"Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007f837d042800 nid=0x3f03 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007f837c005800 nid=0x2603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"T2" #11 prio=5 os_prio=31 tid=0x00007f837c8ed000 nid=0x3e03 waiting for monitor entry [0x0000700003f70000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.hardydou.jmm.DeadLock.doSomeThing(DeadLock.java:29)
- waiting to lock <0x000000079570f7d0> (a java.lang.String)
- locked <0x000000079570f800> (a java.lang.String)
at com.hardydou.jmm.DeadLock.run(DeadLock.java:37)

"T1" #10 prio=5 os_prio=31 tid=0x00007f837c858000 nid=0x3c03 waiting for monitor entry [0x0000700003e6d000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.hardydou.jmm.DeadLock.doSomeThing(DeadLock.java:29)
- waiting to lock <0x000000079570f800> (a java.lang.String)
- locked <0x000000079570f7d0> (a java.lang.String)
at com.hardydou.jmm.DeadLock.run(DeadLock.java:37)

"Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007f837c828000 nid=0x3903 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007f837c076800 nid=0x3803 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007f837b815000 nid=0x4603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007f837d03e800 nid=0x4703 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007f837d01e800 nid=0x4903 runnable [0x000070000385b000]
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 <0x00000007957af218> (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 <0x00000007957af218> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007f837c827000 nid=0x360b waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007f837c842800 nid=0x2f03 in Object.wait() [0x0000700003655000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795588ec0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x0000000795588ec0> (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=31 tid=0x00007f837c016000 nid=0x5203 in Object.wait() [0x0000700003552000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795586b68> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000795586b68> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=31 tid=0x00007f837d00a000 nid=0x2e03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007f837d004800 nid=0x1f07 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007f837d005800 nid=0x1e03 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007f837d006000 nid=0x2b03 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007f837d006800 nid=0x2d03 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007f837d042000 nid=0x4303 waiting on condition

JNI global references: 33


Found one Java-level deadlock:
=============================
"T2":
waiting to lock monitor 0x00007f837c01c0a8 (object 0x000000079570f7d0, a java.lang.String),
which is held by "T1"
"T1":
waiting to lock monitor 0x00007f837c0196b8 (object 0x000000079570f800, a java.lang.String),
which is held by "T2"

Java stack information for the threads listed above:
===================================================
"T2":
at com.hardydou.jmm.DeadLock.doSomeThing(DeadLock.java:29)
- waiting to lock <0x000000079570f7d0> (a java.lang.String)
- locked <0x000000079570f800> (a java.lang.String)
at com.hardydou.jmm.DeadLock.run(DeadLock.java:37)
"T1":
at com.hardydou.jmm.DeadLock.doSomeThing(DeadLock.java:29)
- waiting to lock <0x000000079570f800> (a java.lang.String)
- locked <0x000000079570f7d0> (a java.lang.String)
at com.hardydou.jmm.DeadLock.run(DeadLock.java:37)

Found 1 deadlock.

Heap
PSYoungGen total 38400K, used 7323K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, 22% used [0x0000000795580000,0x0000000795ca6e80,0x0000000797600000)
from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
to space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
ParOldGen total 87552K, used 0K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, 0% used [0x0000000740000000,0x0000000740000000,0x0000000745580000)
Metaspace used 3818K, capacity 4540K, committed 4864K, reserved 1056768K
class space used 423K, capacity 428K, committed 512K, reserved 1048576K


Process finished with exit code 137 (interrupted by signal 9: SIGKILL)

jhat

jhat(JVM Heap Analysis Tool)虚拟机堆转储快照分析工具(分析dump文件),内嵌html服务器,可以通过浏览器查看分析结果。不建议使用。
结果以包为集合分组显示。主要看里面的 Heap Histogram 项目(与jmap -histo 功能一样)与OQL页签功能。

jstack

Java堆栈跟踪工具jstack(Stack Trace for Java),用于获取虚拟机当前时刻的线程快照(每一条线程正在执行的方法栈的集合)。目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。

JDK1.5中,java.lang.Thread 新增一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象,有同样的效果。

jstack命令格式:

jstack [option] vmid

jstack命令主要选项:
upload successful

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
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
2019-05-27 13:46:49
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):

"Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007fed89037800 nid=0x3f03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fed8985f000 nid=0x2703 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"T2" #11 prio=5 os_prio=31 tid=0x00007fed8a0ed800 nid=0x3d03 waiting for monitor entry [0x000070000acde000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.hardydou.jmm.DeadLock.doSomeThing(DeadLock.java:29)
- waiting to lock <0x000000079570f8d8> (a java.lang.String)
- locked <0x000000079570f908> (a java.lang.String)
at com.hardydou.jmm.DeadLock.run(DeadLock.java:37)

Locked ownable synchronizers:
- None

"T1" #10 prio=5 os_prio=31 tid=0x00007fed8a0ed000 nid=0x4303 waiting for monitor entry [0x000070000abdb000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.hardydou.jmm.DeadLock.doSomeThing(DeadLock.java:29)
- waiting to lock <0x000000079570f908> (a java.lang.String)
- locked <0x000000079570f8d8> (a java.lang.String)
at com.hardydou.jmm.DeadLock.run(DeadLock.java:37)

Locked ownable synchronizers:
- None

"Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007fed8a0e1800 nid=0x3a03 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"C1 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007fed8a0a7000 nid=0x4603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007fed8a0a6000 nid=0x3803 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007fed8985e800 nid=0x3603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007fed8981c800 nid=0x4903 runnable [0x000070000a5c9000]
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 <0x00000007957af110> (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 <0x00000007957af110> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

Locked ownable synchronizers:
- None

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

Locked ownable synchronizers:
- None

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fed89807800 nid=0x5003 in Object.wait() [0x000070000a3c3000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795588ec0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x0000000795588ec0> (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)

Locked ownable synchronizers:
- None

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

Locked ownable synchronizers:
- None

"VM Thread" os_prio=31 tid=0x00007fed89807000 nid=0x2c03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fed8a80d800 nid=0x2007 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fed8a80e000 nid=0x2303 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fed8a80e800 nid=0x2a03 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fed8a80f800 nid=0x5303 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007fed8a026800 nid=0x4403 waiting on condition

JNI global references: 33


Found one Java-level deadlock:


JConsole

可视化工具,包含:概述、内存、线程、类、vm摘要、MBean 等6个标签。

upload successful

upload successful

upload successful

upload successful

  • 不同收集器日志格式不同
  • 不同收集器日志有一些共性
  • gc日志需要相对应开关控制

GC参数汇总

gc参数总结

GC日志参数

-verbose:gc

输出gc详细情况,效果等同于:-XX:+PrintGC

-verbose:gc  
稳定版本  
参见:[官方文档](http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html) 

-XX:+PrintGC  
非稳定版本,可能在未通知的情况下删除,在下面官方文档中是-XX:-PrintGC。  
因为被标记为manageable,所以可以通过如下三种方式修改:
1. com.sun.management.HotSpotDiagnosticMXBean API
2. JConsole
3. jinfo -flag
参见:[官方文档](http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html)

输出格式:

1
2
3
[GC类型(gc原因)gc前占用空间->gc后占用空间(堆总空间),gc消耗时间(秒)]
[GC (System.gc()) 7891K->1320K(62976K), 0.0018702 secs]
[Full GC (System.gc()) 1320K->1202K(62976K), 0.0112032 secs]

-XX:+PrintGCDetails

打印gc详细信息、添加后 -verbose:gc(-XX:+PrintGC)自动失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
GC类型 (GC原因)
[代区域标识:gc前占用空间->gc后占用空间(代总空间) ]
……
总gc前占用空间->总gc后占用空间(堆空间),
[元数据空间:gc前占用空间->gc后占用空间(元数据总空间)],gc总消耗时间(秒)]
[时间分布:用户时间 系统时间 真实消耗时间]
由于多cpu的原因,用户时间+系统时间≥真实消耗时间

[GC (System.gc()) [PSYoungGen: 9974K->2320K(18944K)] 9974K->3352K(62976K), 0.0075374 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 2320K->0K(18944K)] [ParOldGen: 1032K->3244K(44032K)] 3352K->3244K(62976K), [Metaspace: 5059K->5059K(1056768K)], 0.0083919 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]

内存空间明细:

Heap
PSYoungGen total 18944K, used 1524K [0x00000007beb00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 16384K, 9% used [0x00000007beb00000,0x00000007bec7d110,0x00000007bfb00000)
from space 2560K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007bfd80000)
to space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000)
ParOldGen total 44032K, used 795K [0x00000007bc000000, 0x00000007beb00000, 0x00000007beb00000)
object space 44032K, 1% used [0x00000007bc000000,0x00000007bc0c6e20,0x00000007beb00000)
Metaspace used 5370K, capacity 5488K, committed 5632K, reserved 1056768K
class space used 619K, capacity 656K, committed 768K, reserved 1048576K

-XX:+PrintGCTimeStamps

最前面增加gc发生时间(jvm启动到gc发生时刻的秒数),其余格式无变化。
同上面两个参数配合使用。

1
2
0.484: [GC (System.gc())  4960K->3982K(62976K), 0.0014751 secs]
0.485: [Full GC (System.gc()) 3982K->1829K(62976K), 0.0105126 secs]

GC触发原因

  • “System.gc()”;
  • “FullGCAlot”;
  • “ScavengeAlot”;
  • “Allocation Profiler”;
  • “JvmtiEnv ForceGarbageCollection”;
  • “GCLocker Initiated GC”;
  • “Heap Inspection Initiated GC”;
  • “Heap Dump Initiated GC”;
  • “WhiteBox Initiated Young GC”;
  • “WhiteBox Initiated Concurrent Mark”;
  • “WhiteBox Initiated Full GC”;
  • “Update Allocation Context Stats”;
  • “No GC”;
  • “Allocation Failure”;
  • “Tenured Generation Full”;
  • “Metadata GC Threshold”;
  • “Metadata GC Clear Soft References”;
  • “CMS Generation Full”;
  • “CMS Initial Mark”;
  • “CMS Final Remark”;
  • “CMS Concurrent Mark”;
  • “Old Generation Expanded On Last Scavenge”;
  • “Old Generation Too Full To Scavenge”;
  • “Ergonomics”;
  • “G1 Evacuation Pause”;
  • “G1 Humongous Allocation”;
  • “Diagnostic Command”;
  • “unknown GCCause”;

常见GC触发原因

System.gc()

系统主动调用
示例代码

1
2
3
4
5
6
7
8
/**
* 系统主动调用GC
* -XX:+PrintGC -XX:PrintGCTimeStamps
*/
@Test
public void sence4() {
System.gc();
}

GC日志

1
2
0.323: [GC (System.gc())  8665K->1288K(125952K), 0.0040810 secs]
0.327: [Full GC (System.gc()) 1288K->1202K(125952K), 0.0120565 secs]

GC发生原因:System.gc()

Allocation Failure

  • Java heap space
  • PermGen space
  • MetaData Space

Java Heap Space

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* GC 触发原因:
* <p>
* Allocation Failure
* <p>
* Java heap space(堆内存不够)
* <p>
* -Xmx100m -Xmn100m -XX:+PrintGC -XX:+PrintGCTimeStamps
*/
@Test
public void sence5() {
int _1MB = 1024 * 1024;
byte[] bigSize = new byte[_1MB * 80];
}

日志

1
2
3
4
5
0.364: [GC (Allocation Failure)  10853K->1384K(90112K), 0.0050521 secs]
0.370: [GC (Allocation Failure) 1384K->1280K(90112K), 0.0028788 secs]
0.372: [Full GC (Allocation Failure) 1280K->1203K(90112K), 0.0110979 secs]
0.384: [Full GC (Allocation Failure) 1203K->1141K(90112K), 0.0052807 secs]
java.lang.OutOfMemoryError: Java heap space

在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分一下这几个概念。

1.全局字符串池(string pool也有叫做string literal pool)

全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。)。
在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

2.class文件常量池(class constant pool)

我们都知道,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。
符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可(它与直接引用区分一下,直接引用一般是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄)。一般包括下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池的每一项常量都是一个表,一共有如下表所示的11种各不相同的表结构数据,这每个表开始的第一位都是一个字节的标志位(取值1-12),代表当前这个常量属于哪种常量类型。
常量池的项目类型
upload successful
每种不同类型的常量类型具有不同的结构,具体的结构本文就先不叙述了,本文着重区分这三个常量池的概念(读者若想深入了解每种常量类型的数据结构可以查看《深入理解java虚拟机》第六章的内容)。

3.运行时常量池(runtime constant pool)

当java文件被编译成class文件之后,也就是会生成我上面所说的class常量池,那么运行时常量池又是什么时候产生的呢?

jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在上面我也说了,class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

举个实例来说明一下:

1
2
3
4
5
6
7
8
String str1 = "abc";
String str2 = new String("def");
String str3 = "abc";
String str4 = str2.intern();
String str5 = "def";
System.out.println(str1 == str3);//true
System.out.println(str2 == str4);//false
System.out.println(str4 == str5);//true

上面程序的首先经过编译之后,在该类的class常量池中存放一些符号引用,然后类加载之后,将class常量池中存放的符号引用转存到运行时常量池中,然后经过验证,准备阶段之后,在堆中生成驻留字符串的实例对象(也就是上例中str1所指向的”abc”实例对象),然后将这个对象的引用存到全局String Pool中,也就是StringTable中,最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询StringTable,保证StringTable里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。

回到上面的那个程序,现在就很容易解释整个程序的内存分配过程了,首先,在堆中会有一个”abc”实例,全局StringTable中存放着”abc”的一个引用值,然后在运行第二句的时候会生成两个实例,一个是”def”的实例对象,并且StringTable中存储一个”def”的引用值,还有一个是new出来的一个”def”的实例对象,与上面那个是不同的实例,当在解析str3的时候查找StringTable,里面有”abc”的全局驻留字符串引用,所以str3的引用地址与之前的那个已存在的相同,str4是在运行的时候调用intern()函数,返回StringTable中”def”的引用值,如果没有就将str2的引用值添加进去,在这里,StringTable中已经有了”def”的引用值了,所以返回上面在new str2的时候添加到StringTable中的 “def”引用值,最后str5在解析的时候就也是指向存在于StringTable中的”def”的引用值,那么这样一分析之后,下面三个打印的值就容易理解了。

总结

  1. 全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。
  2. class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
  3. 运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

转载:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

概述

GC(Garbage Collection),1960年在MIT的Lisp语言。在java中被发扬光大。GC主要控制内存区域是:堆、方法区(元数据空间)。GC主要完成的3件事情:

  • 哪些内存需要回收?(Who)
  • 什么时候回收?(When)
  • 如何回收?(How)

学习目的,对GC的监控和调节。

确定需要回收对象

引用计数算法

每个对象都有一个引用计数器,为0时回收。

  • 逻辑简单清晰
  • 循环引用无法回收

循环引用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ReferenceObj {
public Object ref = null;
static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[10 * _1MB];
}

@Test
public void sence1() {
ReferenceObj one = new ReferenceObj();
ReferenceObj two = new ReferenceObj();
one.ref = two;
two.ref = one;
one = null;
two = null;
System.gc();
}
//[GC (System.gc()) 28413K->21768K(62976K), 0.0033033 secs]
//[Full GC (System.gc()) 21768K->1192K(62976K), 0.0085810 secs]

循环引用对象也被回收了。这说明当前虚拟机并不是通过 引用计数算法来判断对象是否可回收的。

可达性分析算法

猪油语言Java,C#,Lisp 都是通过可达性分析(Reachability Analysis)来判断对象是否存货的。算法基本思路是通过一系列的成为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。
upload successful

可以作为GC Roots的对象包含如下:

  • 虚拟机栈 (栈帧中的本地变量表)中引用对象。
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即Native方法)引用对象

引用

JDK1.2之前引用有两种状态:被引用 or 没有被引用。JDK1.2之后引入了:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

这四种引用强度依次减弱。

强引用

Object obj=new Object();

obj 就是强引用,只要强引用存在 就不会回收。

软引用

SoftReference<ReferenceObj> softRef = new SoftReference(new ReferenceObj("softRef"));

抛出异常前回收

弱引用

WeakReference<ReferenceObj> weakRef = new WeakReference(new ReferenceObj("weakRef"));

下次垃圾回收执行时回收

虚引用

PhantomReference<ReferenceObj> phantomRef = new PhantomReference(new ReferenceObj("phantomRef"), new ReferenceQueue());

下次垃圾回收执行时回收,永远无法获取到被引用对象。理论上可以通过虚引用+ finalize 获取gc执行情况。

示例代码

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
/**
* 强引用、软引用、弱引用、虚引用 测试
* <p>
* 强引用:引用存在,永远不会回收。
* <p>
* 软引用:抛出异常前回收。
* <p>
* 弱引用:下次垃圾回收时回收。
* <p>
* 虚引用:下次垃圾回收时回收,永远无法获取被引用对象。
*/
@Test
public void sence2() {

ReferenceObj strongRef = new ReferenceObj("strongRef");
SoftReference<ReferenceObj> softRef = new SoftReference(new ReferenceObj("softRef"));
WeakReference<ReferenceObj> weakRef = new WeakReference(new ReferenceObj("weakRef"));
PhantomReference<ReferenceObj> phantomRef = new PhantomReference(new ReferenceObj("phantomRef"), new ReferenceQueue());
System.out.println("strongRef : " + strongRef);
System.out.println("softRef : " + softRef.get());
System.out.println("weakRef : " + weakRef.get());
System.out.println("phantomRef : " + phantomRef.get());
System.out.println("=====================GC======================");
System.gc();
System.out.println("strongRef : " + strongRef);
System.out.println("softRef : " + softRef.get());
System.out.println("weakRef : " + weakRef.get());
System.out.println("phantomRef : " + phantomRef.get());
System.out.println("=====================Alloc(40M)======================");
byte[] bs = new byte[1024 * 1024 * 40];
System.out.println("strongRef : " + strongRef);
System.out.println("softRef : " + softRef.get());
System.out.println("weakRef : " + weakRef.get());
System.out.println("phantomRef : " + phantomRef.get());
}

运行结果

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
strongRef : com.hardydou.jmm.ReferenceObj@694f9431
softRef : com.hardydou.jmm.ReferenceObj@f2a0b8e
weakRef : com.hardydou.jmm.ReferenceObj@593634ad
phantomRef : null
=====================GC======================
0.337: [GC (System.gc()) [PSYoungGen: 11990K->2368K(18944K)] 11990K->5448K(62976K), 0.0045934 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
0.342: [Full GC (System.gc()) [PSYoungGen: 2368K->0K(18944K)] [ParOldGen: 3080K->5298K(44032K)] 5448K->5298K(62976K), [Metaspace: 5049K->5049K(1056768K)], 0.0075451 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
strongRef : com.hardydou.jmm.ReferenceObj@694f9431
[ com.hardydou.jmm.ReferenceObj@593634ad ] weakRef run finalize
[ com.hardydou.jmm.ReferenceObj@11adfb87 ] phantomRef run finalize
softRef : com.hardydou.jmm.ReferenceObj@f2a0b8e
weakRef : null
phantomRef : null
=====================Alloc(40M)======================
0.351: [GC (Allocation Failure) [PSYoungGen: 641K->32K(18944K)] 5940K->5330K(62976K), 0.0010440 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
0.352: [GC (Allocation Failure) [PSYoungGen: 32K->32K(18944K)] 5330K->5330K(62976K), 0.0009079 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.353: [Full GC (Allocation Failure) [PSYoungGen: 32K->0K(18944K)] [ParOldGen: 5298K->3907K(32256K)] 5330K->3907K(51200K), [Metaspace: 5052K->5052K(1056768K)], 0.0064555 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
0.360: [GC (Allocation Failure) [PSYoungGen: 0K->0K(18944K)] 3907K->3907K(62976K), 0.0003754 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.360: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(18944K)] [ParOldGen: 3907K->3845K(44032K)] 3907K->3845K(62976K), [Metaspace: 5052K->5049K(1056768K)], 0.0078142 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[ com.hardydou.jmm.ReferenceObj@f2a0b8e ] softRef run finalize
java.lang.OutOfMemoryError: Java heap space
Heap
PSYoungGen total 18944K, used 1127K [0x00000007beb00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 16384K, 6% used [0x00000007beb00000,0x00000007bec19cb0,0x00000007bfb00000)
from space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000)
to space 2560K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007bfd80000)
ParOldGen total 44032K, used 3845K [0x00000007bc000000, 0x00000007beb00000, 0x00000007beb00000)
object space 44032K, 8% used [0x00000007bc000000,0x00000007bc3c14d8,0x00000007beb00000)
Metaspace used 5130K, capacity 5184K, committed 5248K, reserved 1056768K
class space used 601K, capacity 624K, committed 640K, reserved 1048576K

Process finished with exit code 255

非生即死吗?

在可达性分析算法中不可达的对象并非是”非生即死“,还有“缓刑”阶段。一个对象至少要经过两次标记过程.
upload successful
示例

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
class FinalizeEscapeGC {
public void isAlive() {
System.out.println("yes i am still alive !");

}
@Override
protected void finalize() throws Throwable {
GCTest.HOOK = this;
System.out.println("FinalizeEscapeGC.finalize");
}
}

public static FinalizeEscapeGC HOOK = null;
/**
* 1、对象可以再被GC市进行自我拯救
* 2、自我拯救机会只有1次,因为一个对象finalize()方法最多只会被执行1次。
*/
@Test
public void sence3() throws Exception {
HOOK = new FinalizeEscapeGC();
HOOK = null;
System.gc();
Thread.sleep(500L);
if (HOOK != null) {
HOOK.isAlive();
} else {
System.out.println("no, i am dead !");
}
System.out.println("=======second========");
HOOK = null;
System.gc();
Thread.sleep(500L);
if (HOOK != null) {
HOOK.isAlive();
} else {
System.out.println("no, i am dead !");
}

}

执行结果

1
2
3
4
5
6
7
8
0.344: [GC (System.gc()) [PSYoungGen: 7891K->1312K(18944K)] 7891K->1320K(62976K), 0.0020721 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.346: [Full GC (System.gc()) [PSYoungGen: 1312K->0K(18944K)] [ParOldGen: 8K->1201K(44032K)] 1320K->1201K(62976K), [Metaspace: 5060K->5060K(1056768K)], 0.0106312 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
FinalizeEscapeGC.finalize
yes i am still alive !
=======second========
0.866: [GC (System.gc()) [PSYoungGen: 655K->32K(18944K)] 1857K->1233K(62976K), 0.0011709 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
0.867: [Full GC (System.gc()) [PSYoungGen: 32K->0K(18944K)] [ParOldGen: 1201K->834K(44032K)] 1233K->834K(62976K), [Metaspace: 5063K->5063K(1056768K)], 0.0195988 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
no, i am dead !

回收方法区

主要回收:废弃常量,无用的类。

方法区有哪些数据

  • 已加载类信息
  • 类型常量池
  • 域(Field)信息
  • 方法(Method)信息
  • 所有静态(static)变量(不包含常量)

详情可以看 Java几种常量池

找到废弃变量

这个比较简单,变量不存在引用就可以直接回收。(待细细挖掘)

找到无用类

这个比较复杂,需要满足三个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class 对象没有任何地方引用,无法再任何地方通过反射访问该类的方法。

虚拟机可以对满足以上3个条件的无用类进行回收,并不是一定会回收。HotSpot虚拟机可以通过 -Xnoclassgc 参数控制,通过-verbose:class 以及 -XX:TraceClassLoading、-XX:TraceClassUnLoading(需要FasetDebug版本的虚拟机支持) 查看类加载卸载信息。

垃圾收集算法

不同虚拟机平台操作内存方法不同,GC算法也不相同。以下是几种典型的思想。

标记-清除算法

标记-清除(Mark-Sweep)这是最基本的算法,先标记需要清理的垃圾,再对已经标记的垃圾进行回收。此方法不足:

  • 效率低
  • 大量内存碎片

其它算法都是基于对此算法缺点优化所诞生的。
upload successful

复制算法

将空间分成A、B两等分。同一时间只启用一块空间,当A空间满了,就将存活对象复制到B空间,同时A空间全部销毁。B空间满的时候再将存活对象复制到A空间…如此循环。次算法解决了 内存碎片问题、同时效率也得到了提升。但带来新的缺点:

  • 内存使用率低
  • 对象存活率高时,大量复制导致效率变低;

复制算法-图示:
upload successful

实际使用情况:

  • 基点:98%对象都是朝生夕死。下面所提到使用都是基点成立时最佳。
  • 所有商业虚拟机都在新生代采用复制算法作为垃圾回收算法。
  • 非1:1分配,一块较大的Eden、两块较小Survivor空间。每次使用Eden+一块Survivor,回收时将Eden、Survivor中存活对象复制到另一块 Survivor,然后清理掉Eden与Survivor。当 Survivor内存不够时需要借用老年代内存进行分配担保(Handle Promotion)。
  • 默认情况下 HotSpot虚拟机,Eden:Survivor(8:1),也就是说默认情况浪费10%的内存

标记-整理算法

根据老年代特点所提出 标记-整理(Mark-Compact)算法,标记阶段与 标记-清除一样,只不过清除阶段不是直接回收,而是向一端移动,然后清理掉边界以外内存。

upload successful

分代收集算法

当前商业虚拟机的垃圾收集都有采用”分代收集”(Generational Collection)算法,这种算法并非新思路。而是根据对象存活周期的不同将内存划分为几块。一般是把Java堆划分为新生代,老年代。然后根据各个年代的特点采用最适合的收集算法。

HotSpot 算法实现

枚举根节点

可作为GC Roots的节点主要在全局性的引用(常量、类静态属性)与上下文(栈帧中的本地变量)中。GC停顿:gc执行时所有Java执行线程必须停顿(Stop The World)。即时在号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
准确式GC:Jvm知道内存中某个位置数据是什么类型,不用扫描就可以快速找到对象引用的位置。

安全点

在OopMap协助下可以快速枚举出根节点。但如果每条指令都操作OopMap 将会占用大量空间,因此 jvm只在一些特定的位置操作OopMap,这些位置就是安全点(SafePoint)。如何确保所有线程都是在安全点停顿呢?

  • 抢先式中断(Preemptive Suspension)
    GC发生时,所有线程先中断。再检查中断线程是否在安全点,不在再激活让他跑到安全点上。所有虚拟机不再使用这个方法。
  • 主动式中断(Voluntary Suspension)  
    当GC要中断线程时,不直接对线程进行操作,设置一个标识,所有线程去轮询标识,发现中断标识就自行挂起。轮询标志的地方和安全点是重合的。

安全区域

使用SafePoint 解决了如何进入GC的问题,但实际情况并不一定。假如某些程序一直处于sleep或者Blocked状态,这样就无法进入安全点挂起,该如何处理。这就需要安全区(Safe Region)来解决。
安全区是指一段代码片段中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。也可以吧Safe Region 看做是被扩展的Safepoint。
在线程执行到Safe Region中的代码时,首先标识自己进入Safe Region,那样当这段时间JVM发起GC时,就不用管标识自己Safe Region状态的线程了。在线程要离开Safe Region时,要检查系统是否已经完成了根节点枚举(或者GC过程),如果完成了那线程就继续执行,否则就必须等待直到可以安全离开SafeRegion的信号为止。

还有一种情况是,既不在SafeRegion 又处于 sleep 或者Blocked 的线程 存在时 怎么处理呢?放弃GC?

垃圾收集器

垃圾回收算法是方法论,垃圾收集器是具体实现。
HotSpot虚拟机垃圾收集器图示:
upload successful
上图涵盖7中作用于不同分代的收集器,连线的收集器可以搭配使用。重点在CMS、G1这两个收集器。

Serial收集器

最基本、最悠久收集器,曾经(dk1.3.1之前)是新生代唯一选择。单线程的收集器(Stop The World),执行它必须停止所有线程。
图示:
upload successful
Stop The World 体验非常差劲,从JDK1.3开始,一直到现在的Jdk1.8 ,HotSpot虚拟机团队为消除或者减少工作线程因为内存回收而导致停顿的努力一直在进行着。从Serial收集器——>Paraller收集器——>Concurrent Mark Sweep(CMS)——>最前沿的G1收集器,越来越复杂,停顿不断再缩短,但还没有办法完全消除。

Serial收集器特点以及应用:

  • 简单高效
  • Client模式下很好选择

ParNew收集器

Serial收集器多线程版本,除了多线程外其余(例如:-XX:SurvivorRation-XX:pretenureSizeThread-XX:HandlePromotionFailure 等)、收集算法、Stop The World 、对象分配规则、回收策略都与Serial完全一样。
upload successful

ParNew收集器特点及应用:

  • Server模式下新生代首选
  • 唯一一个可以与CMS(Jdk1.5推出、划时代意义)收集器配合使用的。
  • 单Cpu中 ParNew收集器 ≤ Serial收集器,
  • 通过-XX:ParallelGCThreads 参数限制垃圾收集线程数

Parallel Scavenge收集器

不同于其他收集器(CMS关注停顿时间)ParallelScavenge 关注吞吐量,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),例如虚拟机总运行100分钟,其中垃圾回收划掉1分钟,那么吞吐量=99%

Serial Old 收集器

SerialOld是Serial收集器的老年代版本,单线程、标记整理算法。client模式下使用、jdk1.5及以前版本与ParallelScavenge 搭配使用、作为CMS后备预案(在并发收集发生Concurrent Mode Failure时使用)
upload successful

Parallel Old 收集器

Parallel Old 是Parallel Scavenge收集器的老年代版本,多线程、标记整理算法。Jdk1.6中开始使用,在此之前ParallelScanvenge比较尴尬(只可以与SerialOld【性能很差】搭配使用)。
Parallel Old 收集器

CMS收集器

Concurrent Mark Sweep 收集器是一种以获取最短回收停顿时间为目的的收集器。标记-清除 算法,过程分为4步:

  • 初始标记(CMS initial mark)、stop the word
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)、stop the word
  • 并发清除(CMS concurrent sweep)

CMS收集器

  • CMS默认启动回收线程数(CPU数量+3)/4,
  • CMS无法处理浮动垃圾(Floating Garbage),CMS并发清理时,用户线程会产生新的垃圾,这些垃圾被称为浮动垃圾。
  • CMS可能出现Concurrent Mode Failure 而导致另一次Full GC
  • CMS运行期间预留内存无法满足程序需求,就会出现“Concurrent Mode Failure”,此时虚拟机会启动后备预案(SerialOld收集器)
    -XX:CMSInitiatingOccupancyFraction(老年代使用比例,触发老年代GC,太低会GC频繁,太高会引起CMF问题)
  • 使用标记-清除算法会导致空间碎片。CMS提供一个-XX:+UseCMSCompactAtFullCollection 开关参数(默认开启)用于FullGC时开启内存碎片合并整理,-XX:CMSFullGCsBeforeCompaction,这个参数用于设置执行多少次不压缩FullGC后跟着执行一次压缩(默认为0)。

G1收集器

G1(Garbage-First)收集器,先进、Jdk6u4开始试用,Jdk7u4 转正。

  • 并行与并发:充分利用多cpu、多核,缩短STW停顿时间、与java线程并发执行。
  • 分代收集:根据对象年龄(新创建、存活一段时间、熬过多次gc)进行区分管理。
  • 空间整合:整体来看是 标记-整理算法,局部两个Region之间是复制算法。
  • 可预测的停顿:建立可以预测的停顿时间模型。

G1堆内存划分为大小相等的region,保留新生、老年代概念,将region与新、老代关联。每个region 里面设置一个RememberedSet记录Ref信息。
不计算维护RememberedSet的步骤,G1操作步骤分为:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting And Evacauation)

upload successful

关键词

  • 伪泛型
  • 针对引用的检查
  • 类型擦除
  • 协变
  • 多态的冲突

介绍

java泛型是伪泛型,java文件编译时会进行类型擦除这样生成的class文件不再包含任何泛型信息。
泛型价值:增加约束,让代码优雅(无需显式的类型转化)。

原始类型

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Math<T> {
abstract T max(T t1, T t2);
}
-----字节码------
abstract max(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

abstract class Math2<T extends Number> {
abstract T max(T t1, T t2);
}
------字节码-----
abstract max(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;

Object 或者 Number 就是T 的原始类型。这是类型擦除的基础思路,也由此产生了多态冲突问题。

类型擦除

文字描述比较枯燥难懂,以下通过3个示例来介绍,类型擦除、针对引用有效、为什么叫做 伪泛型。

示例1

1
2
3
4
5
6
7
8
9
10
11
12
// 携带泛型的对象对应字节码完全一致。
List<String> strList = new ArrayList<String>();
List intList = new ArrayList();
---------字节码------------
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 2

示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 /**
* 约定泛型的list,可以通过反射成功添加 int类型数据进去,通过反射可以正常读取,直接读取时会提示类型转化异常。
*/
@Test
public void typeErasure() {
try {
List<String> strList = new ArrayList();
strList.getClass().getMethod("add", Object.class).invoke(strList, 123);
System.out.println("strList'size :" + strList.size());
System.out.println(strList.getClass().getMethod("get", int.class).invoke(strList, 0));
System.out.println(strList.get(Integer.valueOf(0)));
} catch (Exception e) {
e.printStackTrace();
}
}

示例3

1
2
3
4
泛型针对引用有效。
List<String> strList1 = new ArrayList();//有效果(阿里巴巴规范也提倡这么写)
List<String> strList2 = new ArrayList<String>();//效果同上面
List strList3 = new ArrayList<String>();//无效果

由于 泛型仅仅生存到编译时期,所以称之为 伪泛型。

多态的冲突

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
abstract class Math<T> {
abstract T max(T t1, T t2);
}
-------二进制码---------
abstract max(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

class IntMath extends Math<Integer> {

@Override
Integer max(Integer t1, Integer t2) {
return java.lang.Math.max(t1, t2);
}
}
--------二进制码--------
max(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
synthetic bridge max(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
L0
LINENUMBER 28 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Integer
ALOAD 2
CHECKCAST java/lang/Integer
INVOKEVIRTUAL com/hardydou/jmm/IntMath.max (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
ARETURN

泛型子类重写不是简单的重写,而是重载+重写

实战

基本类型不可以作为泛型

List<int> list;//编译不通过

泛型不可以被实例化

1
2
3
4
class A<T> {
T t=new T();//不可以被直接实例化
.......
}

泛型使用instanceOf

1
2
3
4
5
6
7
List<String> list1 = new ArrayList<>();
//可以
boolean s1 = (list1 instanceof ArrayList<?>);
boolean s3 = (list1 instanceof ArrayList);
//编译报错
boolean s2 = (list1 instanceof ArrayList<String>);

Java内存结构图

upload successful

程序计数器

介绍

程序计数器(Program Counter Register)是一块很小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器(仅是在概念模型,各种虚拟机有可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器。

特征

  • 线程隔离,线程间互不影响。
  • 唯一没有规定OutOfMemoryError内存区域。

Java虚拟机栈

介绍

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的 内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈到出栈的过程。

编译程序代码的时候,就已经确定了局部变量表和操作数栈的大小,而且在方法表的Code属性中写好了。不会受到运行期数据的影响。

upload successful

局部变量表

是一片逻辑连续的内存空间,最小单位是Slot,用来存放方法参数和方法内部定义的局部变量。

Slot

虚拟机没有明确指明一个Slot的内存空间大小。但是boolean、byte、char、short、int、float、reference、returnAddress类型的数据都可以用32位空间或更小的内存来存放。这些类型占用一个Slot。Java中的long和double类型是64位,占用两个Slot。(只有double和long是jvms里明确规定的64位数据类型)

虚拟机如何调用

1
2
3
4
table = Solat[];
table[0] = this;
table[1……n-1];//对应参数1,参数2…… 参数n

Solt复用对GC的影响

idea 启动gc调试
upload successful
测试代码:

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
package com.hardydou.jmm;

import org.junit.Test;

public class SoltGcTest {
/**
* 内存不会回收
* [Full GC (System.gc()) 66856K->66732K(98304K), 0.0117163 secs]
*/
@Test
public void scenes1() {
byte[] bs = placeHolder(64);
System.gc();
}
/**
* bs 作用域结束,内存还不回收
* [Full GC (System.gc()) 66373K->66371K(98304K), 0.0135849 secs]
*/
@Test
public void scenes2() {
{
byte[] bs = placeHolder(64);
}
System.gc();

}
/**
* Solt 重用,内存回收
* [Full GC (System.gc()) 66560K->835K(98304K), 0.0067793 secs]
*/
@Test
public void scenes3() {
{
byte[] bs = placeHolder(64);
}
int a = 0;
System.gc();
}
/**
* 及时回收
* <p>
* [Full GC (System.gc()) 66435K->835K(98304K), 0.0108251 secs]
*/
@Test
public void scenes4() {
{
byte[] bs = placeHolder(64);
bs = null;
}
System.gc();
}
public static byte[] placeHolder(int n) {
System.out.println("SoltGcTest.placeHolder allocation mem : " + n + "m");
byte[] placeholder = new byte[n * 1024 * 1024];
System.out.println("SoltGcTest.placeHolder allocation Success ");
return placeholder;
}
}

操作栈

操作栈又称为操作数栈,是后进先出栈。编译时确定

MAXSTACK = 2

字节码指令

局部变量必须赋值

不同于类变量存在准备阶段。类变量有两次赋初始值的过程:

  1. 准备阶段,赋予系统初始值;
  2. 初始化阶段,赋予程序员定义的初始值;

因此类变量及时在初始化阶段程序员未赋值,也不会影响使用。但是局部变量没有赋值是不可以使用的。
upload successful

栈帧重叠

在概念模型中,两个栈帧作为虚拟机的元素,是完全相互独立的。但是大多虚拟机的实现里都会做一些优化处理,另两个栈帧出现重叠区域。方便共享数据避免额外数据复制传递。
upload successful

动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态链接(Dynamic Linking)。

静态解析:类加载阶段或者第一次使用的时候就转化为直接引用。  
动态链接:每一次运行期间转化为直接引用。

方法调用

解析

编译期间就完全确定,在类装载解析阶段就会把涉及到的符号引用转换为可以确定的直接引用。

字节码指令
  • invokestatic 调用的方法
  • invokespecial 调用的方法
  • invokevirtual 调用 被 final 修饰的 方法
静态解析示例
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void say() {
System.out.println(" Hello word !");
}
/**
* 方法调用-解析-静态解析
* <p>
* INVOKESTATIC com/hardydou/jmm/DynamicLink.say ()V
*/
@Test
public void scene3() {
DynamicLink.say();
}

分派

面向对象基本特征:继承、封装、多态。分派会解释多态特征,如”重载“、”重写“在虚拟机中是如何实现的。
虚拟机是如何找到正确的目标方法的。

字节码指令
  • invokevirtual
静态分派示例-重载

编译期间就完全确定,在类装载解析阶段就会把涉及到的符号引用转换为可以确定的直接引用。

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
static abstract class Human {}
static class Man extends Human {}
static class Woman extends Human {}

public void say(Human human) {
System.out.println("Hello guy !");
}

public void say(Man man) {
System.out.println("Hello man !");

}

public void say(Woman woman) {
System.out.println("Hello woman !");
}

/**
* 方法调用-分派-静态分派示例1
* 代码编译期间就完全确定,
* 在类装载的解析阶段就会把设计的符号引用全部转变为可确定的直接引用
* 父类型引用子类型
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Human;)V
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Human;)V
*/
@Test
public void scene1() {
DynamicLink dc = new DynamicLink();
DynamicLink.Human man = new DynamicLink.Man();
dc.say(man);
man = new DynamicLink.Woman();
dc.say(man);

}
/**
* 方法调用-分派-静态分派示例2
* 子类型引用
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Man;)V
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Woman;)V
*/
@Test
public void scene2() {
DynamicLink dc = new DynamicLink();
DynamicLink.Man man = new DynamicLink.Man();
dc.say(man);
DynamicLink.Woman woman = new DynamicLink.Woman();
dc.say(woman);
}
动态分派示例-重写

运行结果与字节码不一定相同。

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
static abstract class Human {
abstract void say();
}

static class Man extends Human {

@Override
void say() {
System.out.println("Hello man");
}
}

static class Woman extends Human {
@Override
void say() {
System.out.println("Hello woman");
}
}
/**
* 方法调用-分派-动态分派
* <p>
* 实际执行结果与 字节码 不相同
* <p>
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink$Human.say ()V
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink$Human.say ()V
*/
@Test
public void scene5() {
DynamicLink.Human human = new DynamicLink.Man();
DynamicLink.Human woman = new DynamicLink.Woman();
Object obj = new Object();
human.say();
human = new DynamicLink.Woman();
human.say();
}
小结

截止java1.8,java是静态多分派,动态单分派语言。

方法返回地址

当一个方法开始执行后,有两中方式退出:正常完成出口、异常完成出口。无论哪种方法退出,退出后都要返回到方法被调用到的位置,程序才能继续执行,方法返回时需要栈帧中保持一些信息,用来帮助恢复它的上层方法执行状态。

正常完成出口(Normal Method Invocation Completion)

执行引擎遇到任意一个方法返回指令码

 return 

异常完成出口(Abrupt Method Invocation Completion)

方法执行过程中遇到异常,并且这个异常没有在方法体内得到处理。
无返回值

Java堆

介绍

所有线程共享最大内存空间,虚拟机启动时创建。虚拟机规范描述:所有对象实例、数组都要在堆上分配,但随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化。是垃圾收集器管理的主要区域。不同的垃圾回收算法也决定了堆内存细分方式。

  • 逻辑连续即可,不需要物理连续;
  • -Xmx 最大内存
  • -Xms 最小内存

特征

  • 允许抛出 OutOfMemoryError
  • 线程共享
  • 最大一块内存,对象实例、数组 都在这分配空间。
  • GC算法直接决定细分空间;

方法区

介绍

所有线程共享内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却有一个别名:Non-Heap(非堆)目的是与Java堆分开来。

特征

  • 会抛出OutOfMemoryError
  • 线程共享

运行时常量池

运行时常量池(Run Constant Pool)是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

直接内存

介绍

直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,而且也可能会导致OutOfMemoryError异常出现。

存在方式

JDK1.4引入的NIO,Channel、Buffer可以通过Native直接分配堆外内存,然后通过Java堆中的DirectByteBuffer引用。避免Java堆与Native堆中来回复制数据,提升效能明显。

特征

  • 非Java虚拟机内存区域
  • 允许抛出 OutOfMemoryError(物理内存不够用时)
  • Jdk1.4 Nio 引入
  • 不受 -Xmx 影响

介绍

自动化构建、分发工具

安装

准备

安装java 的Jdk 或者Jre

1
2
$ java -version
java version "1.8.0_121"

安装Gradle

MacOs

通过HomeBrew进行安装

1
brew install gradle

WinOs

待完善

验证安装结果

1
2
3
4
gradle -v
------------------------------------------------------------
Gradle 5.4
------------------------------------------------------------

设置中央仓库

将中央仓库设置为阿里代理库,否则总会卡死。

1
2
mkdir -p ~/.gradle/init.d/ 
touch ~/.gradle/init.d/init.gradle

init.gradle 内容

1
2
3
4
5
6
7
8
9
10
11
12
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public‘}
maven { url "https://maven.aliyun.com/repository/spring" }
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
maven { url "https://maven.aliyun.com/repository/spring" }
maven { url "https://maven.aliyun.com/repository/spring-plugin" }
mavenLocal()
mavenCentral()
}
}

构建JavaApplication项目

gradle 构建引导

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
mkdir learn-gradle
cd learn-gradle
gradle init
Select type of project to generate:
1: basic
2: cpp-application
3: cpp-library
4: groovy-application
5: groovy-library
6: java-application
7: java-library
8: kotlin-application
9: kotlin-library
10: scala-library
Enter selection (default: basic) [1..10] 6

Select build script DSL:
1: groovy
2: kotlin
Enter selection (default: groovy) [1..2] 1

Select test framework:
1: junit
2: testng
3: spock
Enter selection (default: junit) [1..3] 1

Project name (default: learn-gradle):
Source package (default: learn.gradle):

BUILD SUCCESSFUL in 1m 22s
2 actionable tasks: 2 executed

项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
learn-gradle 
├── build.gradle
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│   ├── java
│   │   └── learn
│   │   └── gradle
│   │   └── App.java
│   └── resources
└── test
├── java
│   └── learn
│   └── gradle
│   └── AppTest.java
└── resources

运行项目

1
2
3
4
5
6
7
gradle run

> Task :run
Hello world.

BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

Maven迁移到Gradle

1
2
3
cd projectRoot
gradle init --type pom

基于 生成的 build.gradle 自行调整即可

Idea使用Gradle

安装Gradle插件

更多信息看这里

背景

今天用idea学习lombok时遇到一个问题,注解@Getter @Setter 编译通过通过struct视图也可以看到动态添加的 方法,但是运行时就会提示方法不存在。开始以为是依赖引入时机不对,调整后还是没有效果。百度一下需要这么操作:
idea设置AnnotationProcessors
此图为gradle依赖
lombok依赖
作为常年使用eclipse的我觉得有必要搞搞清楚idea的这个AnnotionProcessor是个什么鬼东西了。

注解

注解脑图
(此图来自互联网)

AnnotionProcessor

首先并不是idea创造的东西,而是java提供的编译时注解处理机制。两种编译时解析方案
1. APT(Annotation Processing Tool)
2. Pluggable Annotation Processing API

APT

apt自JDK5产生,JDK7已标记为过期、不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:

  1. api都在com.sun.mirror非标准包下
  2. 没有集成到javac中,需要额外运行
    apt的更多介绍可以参见这里

Pluggable Annotation Processing API

JSR 269,自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:
upload successful

做一个AnnotationProcessor

环境

* Mac(gradle)
* IntelliJ IDEA
* Java1.8

* 构建工具Gradle 语言 Groovy

创建项目

upload successful
upload successful
upload successful
upload successful
upload successful

模块author-processorx

upload successful
upload successful
upload successful
upload successful

创建Annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.hardydou.learn.annotation.processor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface EnableAuthor {
String name() default "authorXXX";
String email() default "author@XX.com";
}

创建Processor

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.hardydou.learn.annotation.processor;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes({"com.hardydou.learn.annotation.processor.EnableAuthor"})
public class AuthorProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnableAuthor.class);

for (Element ele : elements) {
if (ele.getKind() == ElementKind.CLASS) {
EnableAuthor author = ele.getAnnotation(EnableAuthor.class);
System.out.println(ele + "[{author:" + author.name() + "email:" + author.email() + "}");
}
}
return true;
}
}


注册Processor服务

upload successful

模块test

upload successful
upload successful

修改build.gradle文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
plugins {
id 'java'
id 'application'
}

group 'com.hardydou.learn'
version '1.0-SNAPSHOT'
mainClassName = "com.hardydou.learn.annotation.TestProcessor"
sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile project(":author-processor")
annotationProcessor project(":author-processor")
testCompile group: 'junit', name: 'junit', version: '4.12'
}

创建测试类

1
2
3
4
5
6
7
8
9
10
11
12
package com.hardydou.learn.annotation;

import com.hardydou.learn.annotation.processor.EnableAuthor;

@EnableAuthor(name = "hardy", email = "hardydou@163.com")
public class TestProcessor {

public static void main(String[] args) {
System.out.println("TestProcessor.main");
}
}

执行测试

由于事件发生在编译时,所以每次要执行clean 才可以看到效果
upload successful
upload successful
执行结果
upload successful

介绍

架构图是展示软件系统的 整体全貌、组件关系、部署情况、演进方向 视图语言。

作用

传递架构师要表达的信息,快速评估架构的可行性、安全性、以及问题所在。

分类

分类较多,目前以 4+1视图(场景视图、逻辑视图、物理视图、处理流程图、开发视图)、4C(上下文、容器图、组件图、代码图)为主流。

4+1视图

场景视图

描述 参与者与功能用力的关系,通常以用例图表示。
upload successful

逻辑视图

描述软件功能拆解后的组件关系,组件约束和边界,通常用 组件图来表示
//TODO 添加组件图图片

物理视图

描述软件到硬件的映射关系,反映出系统的组件是如何部署到一组计算器节点上的,通常以 部署图表示

处理流程图

描述系统之间、组件之间通信时序,数据的输入输出,反应系统的功能流程与数据流程,通常由时序图和流程图表示。

开发视图

描述系统的模块划分和组成,以及细化到内部包的组成设计,服务于开发人员,反映系统开发实施过程。

4C图

上下文(语境图Context)

语境图

参与者、系统之间的关系,系统状态进度。
抽象说:
人、ofo 是2个独立存在的事物,可以称作context。

容器图

容器图
互相独立存在的应用(占用独立的pid 就可以看做一个容器)例如:

  • 终端应用:
    APP、API接口、WEB
  • Web服务器:
    apache、 nginx
  • Web应用:  
    部署在 Tomcat、jetty、resion、jboss、Weblogic、Websphere 一个应用
  • 数据库应用:
    Mysql、Oracle、Mongodb
  • 其它
    ELK、文件系统、dns服务

    抽象说:
    ofo 可以分解为 车子、APP、其它 这些可以被称为容器。

组件图

这张组件图不是很严格,没有表示清楚组件提供的能力名称。
组件图
容器是由多个组件构成,所有组件应当运行在同一个进程中。

抽象说:
车子 可以分解为 车胎、车把、车蹬子、车座子、链条、车架、其它 这些可以被称为组件。

代码图

类图

抽象说: 外胎、内胎、辐条、轮圈、轴承 生产方式,轮胎 组装方式 。

upload successful
upload successful

加载器类型

启动加载器

  • BootstrapClassLoader
  • C++语言实现
  • 负责加载<JAVA_HOME>\lib (或者 -Xbootclasspath 参数指定路径)目录中的类库加载到内存

扩展加载类

  • ExtensionClassLoader
  • Java实现
  • 负责加载<JAVA_HOME>\lib\ext目录(或者java.ext.dirs系统变量指定的路径)所有类库

应用程序类加载器

  • ApplicationClassLoader
  • 负责加载 classpath 上的指定类库,我们可以直接使用这个类加载器

双亲委派模式

类架加载器在加载一个类时先咨询自己发父亲是否已经加载如果已经加载就直接返回,没有加载子加载器就进行加载。
upload successful