JVM基础
1. JVM组成
1.1 编译与运行图解
https://segmentfault.com/a/1190000019444195
1.2 类加载
1.3 运行时
类实例、内存分配、栈帧结构、字节码执行过程
=》 基本数据类型:执行完成后,直接释放掉
=》 引用数据类型:执行完成后,局部变量表没有了,但堆内存中还存在
1.4 垃圾回收算法
堆分代模型
确定哪些对象需要回收?
引用计数法、可达性分析
什么时候进行回收?
- CPU空闲时
- 堆满时
- 手动执行System.gc()后,尝试回收
如何回收?
相关算法:标记清除算法
、复制算法
、标记整理算法
、分代收集算法
算法好坏标准: 吞吐量越高算法越好,停顿越短算法越好
1.4.1 标记清除算法
=》使用标记清除算法后,效率低、会存在很多不连续的空间,当需要分配很大连续空间时,可能会失败(创建大的数组时),造成内存浪费
1.4.2 复制算法
=》使用一般的内存空间,达到回收条件时,将存活的对象复制到另一半空间,回收另一半的内存空间。内存空间利用率只有一半,适用于对象存活率较低的场景
1.4.3 标记整理算法
=》对不存活的对象进行清除,对存活的对象进行整理,不产生碎片。适用于对象存活率较高的情况。
1.4.4 分代收集算法
=》一种比较智能算法,一般年轻代使用复制算法(edge+survivor:内存空间比较充足),老年代采用标记清除+标记整理算法。老年代对象存活时间较久,回收频率较低。如下图
1.4.5 分区算法
=》将内存分为多个独立空间,每个空间独立使用,分空间回收而非全部GC,提升性能。
1.5 垃圾回收器
垃圾回收器名称 | 介绍 | 使用场景 |
---|---|---|
Serial | -XX:+UseSerialGC :年轻串行(Serial) 老年串行(Serial Old) 复制算法 |
年轻代 单线程处理 |
Serial Old | 标记整理算法 | 老年代 单线程处理 |
ParNew | -XX:+UseParNewGC 新生代并行(ParNew) 老年代串行(Serial Old) |
年轻代 多线程处理 |
Parallel Scavenge | -XX:+UseParallelGC 标记整理算法 |
新生代使用 并行回收收集器 |
Parallel Old | -XX:+UseParallelOldGC 新生代和老年代都使用并行回收收集器 |
|
CMS | -XX:+UseConcMarkSweepGC 应用CMS收集器 标记清除算法实现 |
并发收集、低停顿 易产生碎片 |
G1 | -XX:+UserG1Gc 应用G1收集器-XX:MaxGCPauseMillis 指定最大停顿时间-XX:ParallelGCThreads 设置并行回收的线程数量 |
面向服务端 常用 |
ZGC | 停顿时间不超过10ms; 停顿时间不会随着堆的大小, 或者活跃对象的大小而增加; 支持8MB~4TB级别的堆 (未来支持16TB)。 |
JDK11+ 低延迟垃圾回收器 |
G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。Region的大小是一致的,数值是在1M到32M字节之间的一个2的幂值数,JVM会尽量划分2048个左右、同等大小的Region。 ===> 约64G
1.6 java agent 详细介绍
//java agent是java命令的一个参数。参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:
//这个jar包的MANIFEST.MF 文件必须指定 Premain-Class 项。
//Premain-Class 指定的那个类必须实现 premain()方法。
3.运行原理
// 1. 一般创建对象就是强引用:创建一个对象并它赋值给一个引用,引用是存在JVM中的栈中的
// 2. new WeakReference<User>(u);用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
//ThreadLocal.ThreadLocalMap.Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 3. SoftReference 软引用:只要内存空间足够,垃圾回收器就不会回收它。
// 4. 虚引用:
分析JVM堆栈-CPU信息
1.获取基本信息
#1.查看服务信息 jps -l
[root@puma-master-data-service-5648c99bdb-fmqz9 data]# jps -l
21 /data/app.jar
156 sun.tools.jps.Jps
#2.查看各个服务占用内存、CPU情况 top
top - 11:01:37 up 208 days, 10:47, 0 users, load average: 1.10, 1.62, 1.49
Tasks: 4 total, 1 running, 3 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.4 us, 0.4 sy, 0.0 ni, 97.1 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 65803724 total, 17665148 free, 14010704 used, 34127872 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 47557080 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21 root 20 0 9486624 977.1m 14152 S 0.7 1.5 1:15.30 java
1 root 20 0 15708 2308 1432 S 0.0 0.0 0:00.06 sh
126 root 20 0 15844 2648 1628 S 0.0 0.0 0:00.02 bash
170 root 20 0 59660 2188 1528 R 0.0 0.0 0:00.00 top
2.查看堆内存使用情况 jmap -heap 21
[root@puma-master-data-service-5648c99bdb-fmqz9 data]# jmap -heap 21
Attaching to process ID 21, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2684354560 (2560.0MB)
NewSize = 89456640 (85.3125MB)
MaxNewSize = 894763008 (853.3125MB)
OldSize = 178978816 (170.6875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 257884160 (245.9375MB)
used = 61801960 (58.938941955566406MB)
free = 196082200 (186.9985580444336MB)
23.96500816490629% used
Eden Space:
capacity = 229244928 (218.625MB)
used = 58794576 (56.07087707519531MB)
free = 170450352 (162.5541229248047MB)
25.647056409466124% used
From Space:
capacity = 28639232 (27.3125MB)
used = 3007384 (2.8680648803710938MB)
free = 25631848 (24.444435119628906MB)
10.500924047125286% used
To Space:
capacity = 28639232 (27.3125MB)
used = 0 (0.0MB)
free = 28639232 (27.3125MB)
0.0% used
tenured generation:
capacity = 572243968 (545.734375MB)
used = 399195072 (380.70208740234375MB)
free = 173048896 (165.03228759765625MB)
69.75959456509291% used
41534 interned Strings occupying 4409040 bytes.
#堆内存 = 年轻代 + 年老代 + 永久代
#年轻代 = Eden区 + 两个Survivor区(From和To)
# 扩展
#查看所有对象,包括活跃以及非活跃的
jmap ‐histo <pid> | more
#查看活跃对象
jmap ‐histo:live <pid> | more
#用jmap把进程内存使用情况dump到文件中,再用jhat分析查看
jmap -dump:format=b,file=/tmp/dump.dat <pid>
jmap -dump:format=b,file=/tmp/dump.jprofiler <pid>
#使用jhat 查看
jhat -port 9999 /tmp/dump.dat
#注意如果Dump文件太大,可能需要加上-J-Xmx512m这种参数指定最大堆内存,即
jhat -J-Xmx512m -port 9999 /tmp/dump.dat
#访问: 127.0.0.1:9999 可根据 Other Queries 查询相关信息
3.查看某个进程堆栈信息 jstack pid > 1.log
[root@puma-master-data-service-5648c99bdb-fmqz9 data]# jstack 21
2021-04-14 11:30:25
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"Attach Listener" #355 daemon prio=9 os_prio=0 tid=0x00007f6b50001000 nid=0x1cd waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Keep-Alive-Timer" #354 daemon prio=8 os_prio=0 tid=0x00007f6b2c002000 nid=0x1c2 waiting on condition [0x00007f69d9edd000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at sun.net.www.http.KeepAliveCache.run(KeepAliveCache.java:172)
at java.lang.Thread.run(Thread.java:748)
...
4.jstat (JVM性能监测工具)
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
GC取样: 采样时间间隔为500ms,采样数为40
jstat -gc <pid> 500 40
#内容
[root@puma-master-data-service-6846d5767c-kqj45 data]# jstat -gc 21 500 40
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
38912.0 38912.0 1464.4 0.0 311616.0 15839.8 777980.0 607892.2 110720.0 105606.5 13696.0 12736.2 1548 155.340 86 44.227 199.567
38912.0 38912.0 1464.4 0.0 311616.0 15888.7 777980.0 607892.2 110720.0 105606.5 13696.0 12736.2 1548 155.340 86 44.227 199.567
38912.0 38912.0 1464.4 0.0 311616.0 16705.0 777980.0 607892.2 110720.0 105606.5 13696.0 12736.2 1548 155.340 86 44.227 199.567
38912.0 38912.0 1464.4 0.0 311616.0 16913.0 777980.0 607892.2 110720.0 105606.5 13696.0 12736.2 1548 155.340 86 44.227 199.567
38912.0 38912.0 1464.4 0.0 311616.0 16919.6 777980.0 607892.2 110720.0 105606.5 13696.0 12736.2 1548 155.340 86 44.227 199.567
38912.0 38912.0 1464.4 0.0 311616.0 16966.7 777980.0 607892.2 110720.0 105606.5 13696.0 12736.2 1548 155.340 86 44.227 199.567
# 分析
S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时
#查看GC日志:
13505.847: [GC (Allocation Failure) 562021K->410043K(579588K), 0.0514277 secs]
13506.774: [GC (Allocation Failure) 570235K->395318K(579588K), 0.0138626 secs]
13508.105: [GC (Allocation Failure) 555510K->398902K(579588K), 0.0188130 secs]
13509.450: [GC (Allocation Failure) 559094K->401556K(579588K), 0.0218595 secs]
13509.723: [GC (Allocation Failure) 561748K->437531K(597828K), 0.1176277 secs]
13509.841: [Full GC (Allocation Failure) 437531K->106980K(597828K), 0.2435032 secs]
13510.214: [GC (Allocation Failure) 267172K->133704K(579588K), 0.0744437 secs]
13510.672: [GC (Allocation Failure) 293896K->150153K(579588K), 0.0557379 secs]
13511.879: [GC (Allocation Failure) 310345K->154503K(579588K), 0.0306883 secs]
13516.569: [GC (Allocation Failure) 314695K->155280K(579588K), 0.0129303 secs]
13523.489: [GC (Allocation Failure) 315472K->155662K(579588K), 0.0115767 secs]
出现了频繁GC情况,这种情况是正常的。(数据刷新,不需要保存)
1.如果对象比较小,生命周期比较短,就需要频繁的GC,将这些对象从内存释放掉。
2.如果说对象比较大,生命周期比较长,就不需要频繁的GC,因为它们主要存在于Old Generation。
#GC (Allocation Failure) 表示到达一个区分配失败,最后 Full GC (即:整个过程GC)
#注: GC 并不是把所有资源都释放掉,只能释放掉一部分,然后存在当前区或者一下个区,最后如果Old区不够时,会抛出内存溢出异常
Eden区 -GC->Eden区 -GC-> Survivor区 -GC-> Old区 -FullGC -> Old区->永久区 (如果空间不够,抛出异常)
Survivor区:解决碎片化
模拟流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor 区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些 存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的, 也就导致了内存碎片化。 永远有一个Survivor space是空的,另一个非空的Survivor space无碎片。
5.查看编译统计 jstat -compiler pid
[root@puma-master-data-service-86bdd475b9-g9pzc data]# jstat -compiler 21
Compiled Failed Invalid Time FailedType FailedMethod
14824 3 0 44.94 1 sun/misc/URLClassPath getLoader
6.hprof能够展现CPU使用率,统计堆内存使用情况
hprof(Heap/CPU Profiling Tool)
java -agentlib:hprof[=options] ToBeProfiledClass
java -Xrunprof[:options] ToBeProfiledClass
javac -J-agentlib:hprof[=options] ToBeProfiledClass
CPU Usage Sampling Profiling(cpu=samples)的例子:
java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello 上面每隔20毫秒采样CPU消耗信息,堆栈深度为3,生成的profile文件名称是java.hprof.txt,在当前目录。
CPU Usage Times Profiling(cpu=times)的例子,它相对于CPU Usage Sampling Profile能够获得更加细粒度的CPU消耗信息,能够细到每个方法调用的开始和结束,它的实现使用了字节码注入技术(BCI)
参考文档
- 《Java 虚拟机规范(Java SE 8版)》
- 《深入理解 Java 虚拟机 第二版》
- 《深入理解 Java 虚拟机 第三版》
- 《实战 Java 虚拟机》