目录
基本命令
收集器
Serial收集器
ParNew收集器
Parallel Scavenge收集器
Serial Old
Parallel Old
CMS收集器
G1收集器
ZGC收集器
排查案例
案例1
案例2
案例3
1). 参数查询
java
-server 选择 "server" VM 默认 VM 是 server.
-cp <目录和 zip/jar 文件的类搜索路径>
-classpath <目录和 zip/jar 文件的类搜索路径> 用 ; 分隔的目录,
JAR 档案 和 ZIP 档案列表, 用于搜索类文件。
-D<名称>=<值> 设置系统属性 ,(有时候 可以用 这个选项 给 jvm设置 系统属性,用于逻辑判断)
java -X
-Xms
-Xmx
-Xss
-Xloggc:
java -XX:+PrintFlagsFinal 查看所有的 jvm 设置项
2). 堆设置
首先设置堆初始大小 最大大小
-Xms2g
-Xmx2g
2个设置年轻代的参数(老年代=总大小-年轻代大小):
最小空间: -XX:newSize=1g
最大空间: -XX:MaxnewSize=1g
同时设置 最小空间和最大空间: -Xmn1g
年轻代与老年代的比例:
-XX:NewRatio=2 (默认值),表示年轻代:老年代 = 1:2
年轻代的 Eden、Survivor 比例设置 参数:
-XX:SurvivorRatio=8 (默认值), from:to:eden = 1:1:8
年轻代设置的大,可以减少 gc 次数。如果年轻代太小,可能会频繁gc,导致 STW 很长
如何计算新生代老年代到底分配了多少空间?
一般来说,通过 堆最小空间/3 得到的是 年轻带最小空间,堆最大空间/3 得到的是 年轻代最大空间,因为 年轻代:老年代 = 1:2 ,年轻代是占了 1/3的空间。 得到了年轻代的空间,剩下的就是老年代的空间。
3). 进入老年代 年龄设置
-XX:MaxTenuringThreshold=15 (默认值) 表示 一个对象 经历了 15次 gc 依然存活,则进入老年代
4). 打印gc信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps gc发生的时间信息
-XX:+HeapDumpOnOutOfMemoryError oom时 dump
-XX:HeapDumpPath=e:\d.txt 可以指定dump 文件
5). jdk 自带工具
jps 可以 进程号
jinfo 查看及设置 进程 配置信息
jstack 查询 某进程 所有的 线程栈 信息
jstat jdk自带的分析工具
查看gc情况:
jstat -gc 19896(进程号) 1000(打印间隔时间)
查看老年代、新生代空间分配、使用情况:
jstat -gcoldcapacity pid
jstat -gcnewcapacity pid
jmap
jmap -histo pid
输出所有对象,包含 有效和无效对象(没有引用的对象 也被输出)
不触发GC
jmap -histo:live
输出 存活的对象。
会触发一次GC,然后把剩下存活的对象输出
jmap -dump:format=b,file=e:/a.bin pid
dump堆对象,只包含存活的对象
不触发GC
jmap -dump:live,format=b,file=e:/a.bin pid
dump堆对象,只包含存活的对象
触发GC
jmap -heap 19896 to print java heap summary
#jvm 参数: -Xms2g -Xmx6g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m
jmap -heap 18680
Attaching to process ID 18680, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 6442450944 (6144.0MB) #堆最大
NewSize = 715653120 (682.5MB) #新生代最小值: 1/3 * (堆初始空间)
MaxNewSize = 2147483648 (2048.0MB)#新生代最大值: 1/3 * (堆最大空间)
OldSize = 1431830528 (1365.5MB)#老年代最小值:(堆初始空间 - NewSize)
NewRatio = 2 #新生代 老年代默认占比: 1(新生代):2(老年代)
SurvivorRatio = 8 #新生代中 eden:from:to 8:1:1
MetaspaceSize = 134217728 (128.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 335544320 (320.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 710410240 (677.5MB)
used = 581433808 (554.4984893798828MB)
free = 128976432 (123.00151062011719MB)
81.8447954804255% used
From Space:
capacity = 2621440 (2.5MB)
used = 1277984 (1.218780517578125MB)
free = 1343456 (1.281219482421875MB)
48.751220703125% used
To Space:
capacity = 2621440 (2.5MB)
used = 0 (0.0MB)
free = 2621440 (2.5MB)
0.0% used
PS Old Generation
capacity = 1431830528 (1365.5MB)
used = 1018873976 (971.673942565918MB)
free = 412956552 (393.82605743408203MB)
71.15883870859889% used
#JVM 参数 -Xms2g -Xmx6g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 6442450944 (6144.0MB)
NewSize = 1073741824 (1024.0MB)
MaxNewSize = 1073741824 (1024.0MB)
OldSize = 1073741824 (1024.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 134217728 (128.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 335544320 (320.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 1035993088 (988.0MB)
used = 143000784 (136.3761749267578MB)
free = 892992304 (851.6238250732422MB)
13.803256571534192% used
From Space:
capacity = 9437184 (9.0MB)
used = 9404480 (8.96881103515625MB)
free = 32704 (0.03118896484375MB)
99.65345594618056% used
To Space:
capacity = 18874368 (18.0MB)
used = 0 (0.0MB)
free = 18874368 (18.0MB)
0.0% used
PS Old Generation
capacity = 1451753472 (1384.5MB)
used = 1184475088 (1129.6034698486328MB)
free = 267278384 (254.8965301513672MB)
81.58927192839529% used
6) 其他
Parallel Scavenge 收集器:
查看是否开启 自适应大小功能
jinfo -flag UseAdaptiveSizePolicy pid
-XX:+UseAdaptiveSizePolicy // 表示开启
由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小。
有些情况存在Survivor 被自动调为很小,这个时候YGC回收掉 Eden区后,
还存活的对象进入Survivor 装不下,就会直接晋升到老年代,
导致老年代占用空间逐渐增加,从而触发FULL GC,
可能导致 FULL GC的耗时很长(比如到达几百毫秒)。
可以关闭这个功能:
-XX:-UseAdaptiveSizePolicy
查看使用的是哪一种垃圾收集器:
查看默认垃圾收集器:java -XX:+PrintCommandLineFlags -version
查看当前JVM程序使用的垃圾收集器:
可以使用JDK自带的 jconsole.exe 来查看 使用的是哪种垃圾收集器
也可以使用 jinfo pid 查看进程信息,其中会有 垃圾收集器的参数配置
各种垃圾收集器的参数名,可以通过打印所有的虚拟机参数查看,
如果需要修改垃圾收集器,则通过 -XX:+垃圾收集器名字
UseSerialGC
UseParNewGC
UseParallelGC
UseParallelOldGC
UseConcMarkSweepGC
UseG1GC
以下内容由个人 总结,并记录自己的理解
JVM 启动后,会获取到操作系统为其分配的内存。虚拟机管理这些内存,是通过 将内存划分出不同的内存块,每一块内存 用来存储 不同的数据。
因此,需要明确的一点规则是: 虚拟机管理内存 是 先对内存 进行划分后,再进行管理。
内存划分的区域包括:
程序计数器(pc)、栈(stack)、方法区(method area)、堆(heap area)
栈分为:虚拟机栈 和 本地方法栈
栈,是属于线程私有的,每个线程都会创建一个独立的栈空间,归当前线程私有使用。栈中存放的是 栈帧,栈帧是每个方法执行时 需要的存储空间。
比如 写一个方法时,会有方法入参、返回值、局部变量(基本类型的变量和引用类型的变量)、也会调用另一个方法,这时需要存储 方法返回的 指令地址。 方法中需要使用到的 数据 都是存储在栈帧中的空间内。StackOverflowError OutOfMemoryError
堆
heap 主要用来存储 对象实例。为了重复利用堆空间,对那些 不再使用的对象 所占用的 内存 需要 及时回收。因此,堆空间 也是 GC 收集器主要管理的内存空间。
堆内存回收,需要考虑:
三个W,Who、When、How
1. 哪些对象需要被回收,也就是 需要 标识出 需要被回收的对象
2. 什么时候回收,也就是 触发GC线程执行的时机
3. 如何回收,也就是需要确定 回收算法
Who
第一步标识,是比较难的。
现在都是使用 引用可达性 算法来确定 对象存活。但是 在程序运行过程中,总是会有新的 die 对象的产生,也有一些可能die的对象,最后又活了过来。因此,STW Stop The World,此时会暂停所有的用户线程,造成了 整个JVM被按了暂定键,这时 服务无法对 外提供正常的响应,对于很多 业务来说,很影响用户的使用体验。而 尽量缩短 STW的时间长度,也是GC的一个目标。
知识点1:算法
可达性 分析 Reachability Analysis 的一些词汇:
GC Roots 根对象
引用链 Reference Chain
GC Roots的对象主要包括:
在虚拟机栈(栈帧中的本地变量表)中引用的对象
在方法区中类静态属性引用的对象
在方法区中常量引用的对象
知识点2:引用类型
引用可达性算法 来 标识 哪些 对象 需要被回收。重要的一个判断依据就是 GC Root 对象 引用的。不同的引用类型,GC的操作也是不同的。需要分析一下 引用。
Java中提供了 4种引用类型:
强引用 Strongly Reference
强引用,是存在的最多的一种引用。Object o = new Object(); 通过这种 = 赋值的引用,都是强引用。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
软引用 Soft Reference
软引用,表示 还有用,但非必须的对象。比如:缓存的数据。如果内存快不足时,就会考虑回收软引用 引用的对象。即使 软引用关系还存在。
弱引用 Weak Reference
弱印象,描述那些非必须对象,但是 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。(实际测试发现,并不是这样,发生GC 也会存在不回收的情况,因为内存足够大。)
虚引用 Phantom Reference
知识点3:软 弱 对比
软引用和弱引用 分别在什么场合下使用呢?
软引用主要是 内存不够时 会回收,弱引用是 GC发生 就回收。对于软引用的作用,还好理解,而为什么要定义 一个 GC时就会回收的引用呢?
一个对象,可能会被多个 引用变量引用着。 所以需要 有这种 引用 的 区分。一个引用 是主要的引用,另一个引用是次要的引用。当主要的引用 不存在时,那么 次要的引用 也不需要存在了。这时,就可以用强引用和弱引用同时 引用这个对象。只要强引用不存在了,那么弱引用就不能存在,即 虽然存在弱引用关系,也需要回收这个对象。所以,弱引用的目的就在这。同理,对于软引用,也可以 表达出 不同强度的引用关系,能表达出主次关系来,当主要的引用关系不存在时,如何处理被次要引用 引用着的对象。
知识点4:最后的救赎
protected void finalize() throws Throwable { }
这个方法是Object 类提供的,虚拟机对那些重写了 finalize方法的 没有 GC Root 引用的对象 处理规则是:
1. 将这些对象放入 F-Queue,虚拟机会 自动创建一个 低优先级的 Finalizer 线程 来执行 finalize方法。这里会存在一个问题,如果一个对象的finalizer执行了很长时间,那么其他对象也得等,这就造成了 对象得不到及时回收的问题。
2. F-Queue队列的对象 都执行了 finalizer方法后,再对 队列中的对象 进行 一次标记。如果 标记时发现 存在GC Root 引用,那么不会进行回收;对于仍然没有GC Root引用的对象,则进行 回收。
3. finalizer 方法 只能被执行一次,如果执行后,下一次 就会直接回收。
此图是 Finalizer线程正在执行 finalizer方法:
简单理解:
同Serial,只是 JVM世界被冻结后, 有 多条 垃圾收集线程 在工作。
新生代 收集器
标记-复制算法
多线程 并行 收集
吞吐量优先
吞吐量 = 用户线程运行时间 / (用户线程运行时间 + 垃圾收集线程运行时间)
对于保证吞吐量可以得到什么结果?
A/(A+B) 越高 =》 (A+B)/A 越低 =》 1 + B/A 越低 => B/A 越低 => 垃圾收集运行时间越低 => 垃圾收集越快=》需要回收的空间越小=》自动把 新生代空间 压缩到很小=》频繁GC或提前进入老年代
老年代收集器
老年代收集器
OOM:java.lang.OutOfMemoryError: GC overhead limit exceeded
这是一种 内存不够的 错误类型。 意思 每次 gc 回收的内存 非常少。
出现这种异常,先直接 调大 JVM 的内存分配
应用服务卡死,无响应,OOM 一般排查思路:
1. 查看 服务资源占用情况:
top 命令
2. 查看 是否出现 内存不足导致 进程被杀掉的情况 :
使用Linux 自带命令 dmesg | grep java ,或者 dmesg | grep 'kill|oom|out of memory'
是否有这种: Out of memory: Kill process 28754 (java) score 210 or sacrifice child
3. 查看服务pid
ps -aux | grep java
4. 查看 gc 情况
jstat -gcutil pid 1000
5. 查看 存活对象分布情况
jmap -histo:live pid
6. 导出dump 文件 ,一般 分析内存OOM 都需要 导出dump文件,这步是必须的。
jmap -dump:format=b,file=/pid.bin
7. 分析dump文件
网上查看的一般是MAT
现象:top命令 发现 cpu 占用极高,jstat 频繁gc
怀疑:可能是出现了死循环
排查:
1. 查看 堆内的对象情况
jmap -histo 46800 和 jmap -histo:live 46800 对比后,发现
1: 51031 4212360 [I
2: 16960 1899520 java.util.GregorianCalendar
3: 16960 1628160 sun.util.calendar.Gregorian$Date
4: 16965 950040 sun.util.calendar.ZoneInfo
5: 16959 949704 java.util.Calendar$Builder
Calendar 对象很多。
2. 查看线程栈中,使用到Calendar的调用栈。可以 快速定位到具体的调用链
>jstack 46800
"main" #1 prio=5 os_prio=0 tid=0x0000000003217000 nid=0xe37c runnable [0x0000000002e5e000]
java.lang.Thread.State: RUNNABLE
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
at java.util.Calendar.setTimeInMillis(Calendar.java:1804)
at java.util.Calendar$Builder.build(Calendar.java:1508)
at sun.util.locale.provider.CalendarProviderImpl.getInstance(CalendarProviderImpl.java:88)
at java.util.Calendar.createCalendar(Calendar.java:1666)
at java.util.Calendar.getInstance(Calendar.java:1613)
3. 再看代码,就定位到问题了。 |
4. 通过dump文件定位下问题
dump文件时,不加 live选型,把所有对象都dump下来;然后再加live选型,只dump存活对象;
对两个dump文件对比。
通过 java自带的 jvisualvm 查看
所有对象:
仅存活对象:
可以看出来,有这么多的对象 需要被回收。
也可以猜出出,是 死循环 在不断的 创建对象。
一开始 我以为 是 数据库查询的数据量太多导致 有大量对象被回收,如果真是数据库查询出的话,应该是直接 OOM,而不是频繁GC。
通过这个案例,可以看出,排查问题,需要熟练使用 堆相关命令、栈相关命令 结合使用。
可以用这个代码进行测试:
JVM设置:
-Xms100m -Xmx100m -Xmn50m -XX:+PrintGCDetails
代码:
public static void main(String[] args) {
while (true){
Calendar calendar = Calendar.getInstance();
}
}
内存泄漏,是指 内存 申请后,无法被释放。
另一个概念叫内存溢出,是指 申请的内存 不足以 存放 程序需要存放的数据。
内存溢出,会导致 不可预估的 结果;而内存泄漏,会导致 程序最终因没有内存而无法正常运行。
内存泄漏,不是 没有发生GC,不是 不回收内存,而是 GC 认为 这部分内存 仍然被使用,不应该被回收。 这就像,程序 使用完这部分内存后, 犯了一个错误,没有给GC 发出 回收信号,导致GC认为 这部分内存 仍然有用。 而程序 犯的这个错误,是需要 我们 找出来。
服务器内存占用过高
Java 服务启动后,并没有运行什么程序,内存占用却达到了 90%以上,什么原因?
一般我们说内存占用过高,就是指 服务器的物理机内存占用过高。而Java程序 只是 使用服务器内存的一个程序,还有其他的程序也占用内存。那么,Java 服务 所在的物理机内存占用过高,跟什么频繁GC/Full GC/程序变慢不一样。首先想到的是 JVM 吃了 很多 内存,有两种情况:
1. 初始分配内存过大
也就是 -Xms 设置的内存大小,是JVM 启动时 就要吃掉的内存,如果这个值设置很大,时 程序 运行过程中 没有创建太多的对象,那么 服务器内存 也是会被占用的。
2. JVM 回收内存回收不掉
比如:内存泄漏,导致内存不断的被占用,但是又回收不了,JVM占用的内存朝着 -Xmx 设置的值去发展,最终导致内存慢慢的被吃完了。