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命令
  1. # jstat -options
  2. -class # 类加载信息统计
  3. -compiler # JIT即时编译器相关的统计
  4. -gc # GC相关的堆内存信息 jstat -gc -t 86332 1s 20 # -t JVM启动时间长度
  5. -gccapacity # 各个内存池分代空间的容量
  6. -gccause # 查看上次GC,本次GC
  7. -gcmetacapacity # meta区大小统计
  8. -gcnew # 年轻代统计信息
  9. -gcnewcapacity # 年轻代空间大小统计
  10. -gcold # 老年代和元数据区统计
  11. -gcoldcapacity # old空间大小统计
  12. -gcutil # GC相关区域使用率utilization统计
  13. -printcompilation # 打印JVM编译统计信息

jstat -gc -t PID 1000 1000

  • YGC YGC总次数
  • YGCT YGC总耗时
  • YGC每次消耗时间 = YGCT / YGC
  • FGC FGC总次数
  • FGCT FGC总耗时
  • FGC每次消耗时间 = FGCT / FGC

jstat-gc.png

jstat -gcutil -t PID 1000 1000

  • 每一列都是使用率
  • 可以用上面的U / C 得到使用率

gcutil.png

jmap

  • -heap 打印堆信息内存池的配置和使用信息

jmap-heap.png

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

jmap-histo.png

  • -dump:format=b,file=X.hprof Dump堆内存

    jstack

  • -F 强制执行thread dump,可在Java进程卡死时(hung住)使用,可能需要系统权限

  • -m 混合模式mixedMode,将Java帧和native帧一起输出,可能需要系统权限
  • -l 长列表模式,包含线程相关的锁信息一起输出,如持有的锁,等待的锁

jstack-l.png

jcmd

  • 综合命令
  • jcmd PID help

jcmd.png

JDK内置图形化工具

jconsole

  • 命令行直接输入命令即可打开
    • 本地JVM可以直接选择
    • 远程JVM可以通过JMX方式连接

jconsole.png

jvisualvm

  • 命令行直接输入命令即可打开
  • 增强版jconsole

jvisualvm.png

VisualGC

  • IDE里的一款插件

VisualGC.png

jmc

  • 官方自带的最强大的工具,飞行记录器是动态视频的方式记录动态数据
  • 命令行里执行jmc

jmc.png

GC的背景与原理

GC背景

  • 本质上是内存资源的有限性,所以cpu时间片是无限的所以不用GC
  • 管理方式

    • 手动管理
      • alloc/free
      • 手动申请/手动释放
    • 自动管理
      • GC

        GC原理

  • 引用计数法

    • 对象被引用一次就在引用计数里面加一次
    • 对象的引用计数为0标志着此对象可以被回收
    • 但有的对象有循环依赖,导致有的对象的引用计数最小为1,所以永远不能被回收
    • 对象不能被回收造成内存泄漏,泄漏的多了造成OOM,进而导致程序崩溃
  • 标记清除法Mark&Sweep
    • 改进引用计数中的循环依赖导致引用不能清零的问题采用了引用跟踪进行标记
    • Mark:遍历所有可达对象,并在本地内存native中分门别类标记下
    • Sweep:没有被标记的对象都可以被清理,进而内存复用
    • 是并行GC和CMS的基本原理
    • 优势
      • 可以处理循环依赖
      • 只扫描部分对象,然后从根对象深度遍历标记,提升性能
    • 除了清除,还要做压缩,整理碎片
    • 在运行的程序中标记和清除上百万的对象必然要STW

MS和整理.png

分代假设

  • 大部分的新生对象很快无用
  • 存活较长时间的对象,可能存活更长时间
  • 把内存分为内存池,不同类型对象不同区域,有不同的处理策略
    • YoungGC比较频繁,FullGC比较少
    • 默认15次GC后还存在的对象转移到老年代
    • Eden:S0:S1 = 8:1:1
    • S0/S1永远有一个是空的,所以新建的对象只能占到年轻代的90%

分代假设.png
内存池.png

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

内存周期.png

GC Roots

  • 可以作为GC Roots的对象
    • 当前正在执行的方法里的局部变量和输入参数
    • 活动线程ActiveThreads
    • 所有类的静态字段StaticField
    • JNI引用
  • 此阶段暂停的时间与堆内存大小,对象的总数没有直接关系,而是由存活对象ActiveObjects的数量决定
  • 所以增加堆内存大小并不会直接影响标记阶段占用的时间

GCRoots.png

GC算法

  • 清除算法

清除算法.png

  • 复制算法

复制算法.png

  • 整理算法

整理算法.png

触发GC

  • 主动触发
    • System.gc()
  • 系统触发

    • 不同GC触发条件不一样
    • 大部分是内存不够了或达到了设定的阈值

      串行GC/并行GC

      SerialGC

  • 使用方式

    • -XX:+UseSerialGC
    • -XX:+UseParNewGC
      • 是改进版本的SerialGC,可配合CMS使用
      • 把原来单线程处理的过程改成多线程处理
  • 对年轻代使用标记-复制算法,对老年代使用标记-清除-整理算法
  • 两者都是单线程的垃圾收集器,不能进行并行处理,所以都会触发STW,停止所有应用线程
  • 这种算法不能充分利用CPU,不管有多少CPU内核,GC时只能使用单个核心
  • CPU利用率高,暂停时间长,简单粗暴
  • 适合单核CPU,几百MB堆内存的应用

    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个阶段
  1. InitialMark 初始标记
    1. 需要STW
    2. 标记所有根对象,包括根对象直接引用的对象,以及被年轻代中所有存活对象所引用的对象
    3. 初始标记.png
  2. ConcurrentMark 并发标记
    1. 从第一个阶段的InitialMark找到的根对象开始遍历老年代,标记所有存活对象
    2. 与应用线程同时运行,不需要STW
    3. 并发标记.png
  3. ConcurrentPreclean 并发预清理
    1. 与应用线程并发执行
    2. 如果在第二个阶段中对象的引用关系发生变化,JVM会通过CardMarking的方式将发生改变的区域标记脏区
    3. 并发预清理.png
  4. FinalRemark 最终标记
    1. 是此次GC事件中的第二次也是最后一次STW
    2. 来完成老年代所有存活对象的标记,防止上一阶段的变化
    3. 最终标记.png
  5. ConcurrentSweep 并发清除
    1. 与应用线程并发执行
    2. 无法引用到的对象也不可能再被引用到,所以删除不再使用的对象,并回收内存
    3. 并发清除.png
  6. ConcurrentReset 并发重置
    1. 与应用线程并发执行
    2. 重置CMS算法内部数据,为下一次GC做准备

      G1

  • 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名称的由来
    • G1内存划分.png
    • G1CollectionSet.png
  • 处理步骤
  1. 年轻代模式转移暂停(EvacuationPause)
    1. G1会根据前面运行一段时间的数据来不断调整自己的行为
    2. 刚启动时可能处于fully-young的模式,年轻代空间用满后,应用线程被暂停年轻代的存活对象拷贝到S区
    3. 拷贝的过程称为Evacuation
  2. 并发标记(ConcurrentMarking)
    1. G1的处理基于CMS
    2. 这一阶段基本和CMS的并发标记是一致的
    3. 主要通过起始快照SnapshotAtTheBeginning的方式,在标记开始时记下所有的存活对象
    4. 通过存活信息,构建出每个SHR的存活状态,以便高效的选择
    5. 小阶段
      1. InitialMark
      2. RootRegionScan
      3. ConcurrentMark
      4. Remark
      5. Cleanup
  3. 转移暂停:混合模式(EvacuationPause(mixed))
    1. 并发标记完成后,G1将执行一次混合收集MixedCollection,年轻代、老年代的一部分区域都加入CS回收集
    2. 此步骤不一定紧跟并发标记阶段,规则和历史数据会影响混合模式的启动时机
  • G1某些情况下触发FullGC,这时会退化使用Serial收集器完成GC,GC暂停时间达到秒级别

    • 并发模式失败
      • G1启动标记周期,在MixedGC之前,老年代被填满,这时G1会放弃标记周期
      • 解决办法
        • 增加堆大小
        • 增加GC线程数
        • 调整周期
    • 晋升失败
      • 没有足够的内存供存活对象或晋升对象使用,触发FullGC
      • 解决办法
        • -XX:G1ReservePercent 增加预留内存量
        • 减少-XX:InitiatingHeapOccupancyPercent 提前启动标记周期
        • -XX:ConcGCThreads 增加GC线程
    • 巨型对象分配失败
      • 当巨型对象找不到合适的空间进行内存分配,就会FullGC
      • 解决办法
        • 增加内存
        • 增大SHRS -XX:G1HeapRegionSize
        • 编码时尽量使用小对象

          GC对比、组合、选择

          对比

          各个GC对比.png

          常用组合

          GC组合.png
  • Serial + SerialOld 实现单线程低延迟GC机制

  • ParNew + CMS 实现多线程低延迟GC机制
  • ParallelScavenge+ ParallelScavengeOld 实现多线程高吞吐量GC机制(GC时间长但不影响业务线程)

    如何选择

  • 选择正确的GC,唯一可行的方式就是去尝试,一般的指导性原则如下

    • 系统是吞吐优先,CPU资源尽量来处理业务,用ParallelGC
    • 系统是低延迟优先,每次GC时间尽量短,用CMS GC
    • 系统内存堆较大,同时希望GC时间可控,用G1 GC
  • 对于内存大小的考量

    • 4G以下,基本都差不多
    • 4G以上,用G1的性价比较高
    • 超过8G,非常推荐G1

      ZGC/ShenandoahGC

      ZGC

  • 使用方式

    • -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx16g
  • 特点

    • GC最大停顿时间不超过10ms
    • 堆内存支持范围广,小至MB级别,大至TB级别
    • 与G1相比,吞吐量下降不超过15%
    • JDK15后支持MacOS和Windows,之前只支持Linux
    • 通过着色指针和读屏障,实现几乎全部的并发执行

      ShenandoahGC

  • 使用方式

    • -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -Xmx16g
  • 特点

    • GC暂停时间与堆大小无关,都是很低的暂停时间
    • 立项比ZGC还早,通过实现垃圾回收过程的并发处理,改善停顿时间

      总结

      GC演进

  • 串行->并行:重复利用多核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