JDK内置命令行工具
开发常用
| 工具 | 简介 |
|---|---|
| java | java应用的启动程序 |
| javac | JDK内置的编译工具 |
| javap | 反编译class文件的工具 |
| javah | JNI开发时,根据java代码生成需要的.h文件 |
| javadoc | 根据Java代码和标准注释,自动生成相关的API说明文档 |
| extcheck | 检查某个jar文件和运行时扩展jar 有没有版本冲突,很少使用 |
| jdb | JavaDebugger,可调试本地或远程程序,开发时很少使用 |
| jdeps | 探测class或jar包需要的依赖 |
| jar | 打包工具,.jar文件本质上就是zip文件,只是后缀不同。 |
| keytool | 证书和密钥的管理工具,支持生成、导入、导出等 |
| jarsigner | jar文件签名和验证工具 |
| policytool | 图形界面,管理本机的Java安全策略 |
分析常用
| 工具 | 简介 |
|---|---|
| jps | 查看java进程 |
| jinfo | 查看某一个进程的详细配置信息 |
| jmap | 查看heap或类 |
| jstat | 查看GC |
| jstack | 查看线程 |
| jcmd | 执行JVM相关分析命令(整合上面的命令) |
| jrunscript/jjs | 执行js命令 |
# jstat -options-class # 类加载信息统计-compiler # JIT即时编译器相关的统计-gc # GC相关的堆内存信息 jstat -gc -t 86332 1s 20 # -t JVM启动时间长度-gccapacity # 各个内存池分代空间的容量-gccause # 查看上次GC,本次GC-gcmetacapacity # meta区大小统计-gcnew # 年轻代统计信息-gcnewcapacity # 年轻代空间大小统计-gcold # 老年代和元数据区统计-gcoldcapacity # old空间大小统计-gcutil # GC相关区域使用率utilization统计-printcompilation # 打印JVM编译统计信息
jstat -gc -t PID 1000 1000
- YGC YGC总次数
- YGCT YGC总耗时
- YGC每次消耗时间 = YGCT / YGC
- FGC FGC总次数
- FGCT FGC总耗时
- FGC每次消耗时间 = FGCT / FGC
jstat -gcutil -t PID 1000 1000
- 每一列都是使用率
- 可以用上面的U / C 得到使用率
jmap
- -heap 打印堆信息内存池的配置和使用信息

- -histo 看哪些类占用的空间最多

-dump:format=b,file=X.hprof Dump堆内存
jstack
-F 强制执行thread dump,可在Java进程卡死时(hung住)使用,可能需要系统权限
- -m 混合模式mixedMode,将Java帧和native帧一起输出,可能需要系统权限
- -l 长列表模式,包含线程相关的锁信息一起输出,如持有的锁,等待的锁
jcmd
- 综合命令
- jcmd PID help
JDK内置图形化工具
jconsole
- 命令行直接输入命令即可打开
- 本地JVM可以直接选择
- 远程JVM可以通过JMX方式连接
jvisualvm
- 命令行直接输入命令即可打开
- 增强版jconsole
VisualGC
- IDE里的一款插件
jmc
- 官方自带的最强大的工具,飞行记录器是动态视频的方式记录动态数据
- 命令行里执行jmc
GC的背景与原理
GC背景
- 本质上是内存资源的有限性,所以cpu时间片是无限的所以不用GC
管理方式
引用计数法
- 对象被引用一次就在引用计数里面加一次
- 对象的引用计数为0标志着此对象可以被回收
- 但有的对象有循环依赖,导致有的对象的引用计数最小为1,所以永远不能被回收
- 对象不能被回收造成内存泄漏,泄漏的多了造成OOM,进而导致程序崩溃
- 标记清除法Mark&Sweep
- 改进引用计数中的循环依赖导致引用不能清零的问题采用了引用跟踪进行标记
- Mark:遍历所有可达对象,并在本地内存native中分门别类标记下
- Sweep:没有被标记的对象都可以被清理,进而内存复用
- 是并行GC和CMS的基本原理
- 优势
- 可以处理循环依赖
- 只扫描部分对象,然后从根对象深度遍历标记,提升性能
- 除了清除,还要做压缩,整理碎片
- 在运行的程序中标记和清除上百万的对象必然要STW
分代假设
- 大部分的新生对象很快无用
- 存活较长时间的对象,可能存活更长时间
- 把内存分为内存池,不同类型对象不同区域,有不同的处理策略
- YoungGC比较频繁,FullGC比较少
- 默认15次GC后还存在的对象转移到老年代
- Eden:S0:S1 = 8:1:1
- S0/S1永远有一个是空的,所以新建的对象只能占到年轻代的90%


GC时对象转移
- 对象创建时在新生代的Eden区
- 标记阶段Eden区存活的对象就会复制到存活区
- 两个存活区from/to 对应S0/S1互换角色
- 对象存活到一定周期会提升到老年代(默认是经历15次GC,由-XX:+MaxTenringThreshold=15)
- 老年代默认都是存活对象,采用移动方式,区别于Eden区的复制方式
- 标记所有通过GC根对象 GC roots可达的对象
- 删除所有不可达对象
- 整理老年代空间的内容,将所有存活对象复制,从老年代空间开始的地方依次存放
- 持久代/元数据区
- 1.8之前 -XX:MaxPermSize=256m
- 1.8之后 -XX:MaxMetaspaceSize=156m
GC Roots
- 可以作为GC Roots的对象
- 当前正在执行的方法里的局部变量和输入参数
- 活动线程ActiveThreads
- 所有类的静态字段StaticField
- JNI引用
- 此阶段暂停的时间与堆内存大小,对象的总数没有直接关系,而是由存活对象ActiveObjects的数量决定
- 所以增加堆内存大小并不会直接影响标记阶段占用的时间
GC算法
- 清除算法

- 复制算法

- 整理算法
触发GC
- 主动触发
- System.gc()
系统触发
使用方式
- -XX:+UseSerialGC
- -XX:+UseParNewGC
- 是改进版本的SerialGC,可配合CMS使用
- 把原来单线程处理的过程改成多线程处理
- 对年轻代使用标记-复制算法,对老年代使用标记-清除-整理算法
- 两者都是单线程的垃圾收集器,不能进行并行处理,所以都会触发STW,停止所有应用线程
- 这种算法不能充分利用CPU,不管有多少CPU内核,GC时只能使用单个核心
- CPU利用率高,暂停时间长,简单粗暴
-
ParallelGC
使用方式
- -XX:+UseParallelGC
- -XX:+UseParallelOldGC
- -XX:+UseParallelGC -XX:+UseParallelOldGC
- -XX:ParallelGCThreads=N 指定GC线程数,默认为CPU核心数
- 对年轻代使用标记-复制算法,对老年代使用标记-清除-整理算法
- GC处理都会触发STW
- GC期间,所有的CPU内核都在并行GC,总暂停时间更短
- 两次GC周期的间隔期,没有GC线程运行,不会消耗任何系统资源
-
CMS GC/G1 GC
CMS
Concurrent Mark and Sweep(并行的线程有一部分是业务线程,而上面的并发GC时所有的线程都GC)
- 使用方式
- -XX:+UseConcMarkSweepGC
- 对年轻代采用并行STW方式的标记-复制算法,对老年代使用标记清除算法
- 设计目标是避免老年代GC时长时间卡顿,主要通过两种手段完成
- 不对老年代进行整理,而是使用空闲列表free-lists来管理内存空间的回收,进行老年代的并发回收时,会伴随多次年轻代的minor GC
- 在MarkAndSweep标记清除阶段的大部分工作和应用线程一起并发执行,在这些阶段没有明显的应用线程暂停,但会和应用线程争抢CPU时间,默认情况下,CMS使用的并发线程数等于CPU核心数的1/4
- 如果是多核CPU,并且主要调优目标是降低GC停顿导致的延迟,推荐使用CMS
- 由于对老年代不采用压缩的算法,导致老年代内存碎片比较严重,所以在堆内存比较大的情况下GC时间可能不可控
- GC6个阶段
- InitialMark 初始标记
- 需要STW
- 标记所有根对象,包括根对象直接引用的对象,以及被年轻代中所有存活对象所引用的对象

- ConcurrentMark 并发标记
- 从第一个阶段的InitialMark找到的根对象开始遍历老年代,标记所有存活对象
- 与应用线程同时运行,不需要STW

- ConcurrentPreclean 并发预清理
- 与应用线程并发执行
- 如果在第二个阶段中对象的引用关系发生变化,JVM会通过CardMarking的方式将发生改变的区域标记脏区

- FinalRemark 最终标记
- 是此次GC事件中的第二次也是最后一次STW
- 来完成老年代所有存活对象的标记,防止上一阶段的变化

- ConcurrentSweep 并发清除
- 与应用线程并发执行
- 无法引用到的对象也不可能再被引用到,所以删除不再使用的对象,并回收内存

- ConcurrentReset 并发重置
- GarbageFirst,意为垃圾优先,哪一块的垃圾最多就优先清理它
- 最主要的设计目标是将STW停顿的时间和分布变成可预期可配置的是一款实时GC,可以为其设置某些性能指标
- 使用方式
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis=50 预期每次GC操作过程中的暂停时间,默认200,G1会尽量控制在这个范围内
- -XX:+GCTimeRatio 应用线程和花在GC线程上的时间比率,默认是9,即 9:1
- -XX:G1NewSizePercent 初始年轻代占比,默认5%
- -XX:G1MaxNewSizePercent 最大年轻代占比,默认60%
- -XX:G1HeapRegionSize 设置每个Region大小,单位MB,默认是堆内存的1/2000,但数值必须是2幂次
- -XX:ConcGCThreads 与应用线程并行的GC线程数量,默认是Java线程的1/4
- -XX:InitiatingHeapOccupancyPercent 简称IHOP,内部并行回收循环启动的阈值,默认Heap的45%可以理解为老年代使用大于等于45%的时候,JVM就会启动垃圾回收
- -XX:G1HeapWastePercent G1停止内存回收的最小内存大小,默认是Heap的5%,不必每次都处理完
- -XX:G1MixedGCCountTarget 设置并行循环之后需要多少个混合GC启动,默认8个,老年代的Region回收时间比较长,如果这个混合启动比较多的,可以允许G1延长老年代的收集时间
- -XX:+UseStringDeduplication 手动开启String对象的去重工作,主要用于相同String避免重复申请内存
- -XX:G1TraceConcRefinement VM调试信息
- 特性
- 堆不再分成老年代和年轻代
- 而是划分为多个,通常是2048个可以存放对象的小块堆区域SmallerHeapRegions
- 每一个小块,都承担着Eden、Survivor、Old区的角色
- 逻辑上Eden、Survivor合起来就是年轻代、所有Old区合起来就是老年代
- 这样划分后G1不用每次都整理整个堆空间,而是增量的方式分批处理CollectionSet回收集
- G1的一项创新是在并发阶段估算每个小堆块存活对象的总数
- 构建回收集CollectionSet的原则是垃圾最多的小块被优先收集,也是G1名称的由来


- 处理步骤
- 年轻代模式转移暂停(EvacuationPause)
- G1会根据前面运行一段时间的数据来不断调整自己的行为
- 刚启动时可能处于fully-young的模式,年轻代空间用满后,应用线程被暂停年轻代的存活对象拷贝到S区
- 拷贝的过程称为Evacuation
- 并发标记(ConcurrentMarking)
- G1的处理基于CMS
- 这一阶段基本和CMS的并发标记是一致的
- 主要通过起始快照SnapshotAtTheBeginning的方式,在标记开始时记下所有的存活对象
- 通过存活信息,构建出每个SHR的存活状态,以便高效的选择
- 小阶段
- InitialMark
- RootRegionScan
- ConcurrentMark
- Remark
- Cleanup
- 转移暂停:混合模式(EvacuationPause(mixed))
- 并发标记完成后,G1将执行一次混合收集MixedCollection,年轻代、老年代的一部分区域都加入CS回收集
- 此步骤不一定紧跟并发标记阶段,规则和历史数据会影响混合模式的启动时机
G1某些情况下触发FullGC,这时会退化使用Serial收集器完成GC,GC暂停时间达到秒级别
- 并发模式失败
- G1启动标记周期,在MixedGC之前,老年代被填满,这时G1会放弃标记周期
- 解决办法
- 增加堆大小
- 增加GC线程数
- 调整周期
- 晋升失败
- 没有足够的内存供存活对象或晋升对象使用,触发FullGC
- 解决办法
- -XX:G1ReservePercent 增加预留内存量
- 减少-XX:InitiatingHeapOccupancyPercent 提前启动标记周期
- -XX:ConcGCThreads 增加GC线程
- 巨型对象分配失败
- 并发模式失败
Serial + SerialOld 实现单线程低延迟GC机制
- ParNew + CMS 实现多线程低延迟GC机制
ParallelScavenge+ ParallelScavengeOld 实现多线程高吞吐量GC机制(GC时间长但不影响业务线程)
如何选择
选择正确的GC,唯一可行的方式就是去尝试,一般的指导性原则如下
- 系统是吞吐优先,CPU资源尽量来处理业务,用ParallelGC
- 系统是低延迟优先,每次GC时间尽量短,用CMS GC
- 系统内存堆较大,同时希望GC时间可控,用G1 GC
对于内存大小的考量
使用方式
- -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx16g
特点
使用方式
- -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -Xmx16g
特点
串行->并行:重复利用多核CPU,大幅降低GC暂停时间,提升吞吐量
- 并行->并发:不只开多个GC线程并行回收,还将GC操作分多个步骤,让繁重的任务和应用线程一起并发,减少单次GC暂停时间,降低业务系统延迟
- CMS->G1:G1是在CMS基础上进行迭代和优化开发出来的,划分多个SHR进行增量回收,进一步降低暂停时间
- G1->ZGC: ZGC号称无停顿的GC,在G1的基础上升级了底层算法
- 业务上大部分都可以接收GC在10-100ms之间,往往更多的追求吞吐量即并行GC
推荐使用G1,如果内存非常大或对GC时间比较敏感推荐使用ZGC/ShanandoahGC
JVM默认配置
MaxHeapSize默认为宿主机内存的1/4
- NewSize默认为宿主机的1/64
- Java8之前默认的GC就是ParallelGC,之后的是G1

