【JVM调优】知识点汇总

1. ClassFileFormat

  • 查看16进制格式的ClassFile
    • Sublime / notepad
    • IDEA插件 - BinEd
  • 观察ByteCode的方法
    • javap
    • JBE - 可以直接修改
    • JClassLib - IDEA插件之一

2. 类加载-初始化

  • 加载过程
    • Loading - 加载
    • Linking
      • Verification - 校验
      • Preparation - class静态变量赋默认值
      • Resolution - 符号引用转为能直接访问的内容
    • Initializing - 静态变量赋值为初始值
  • 总结
    • load - 默认值 - 初始值
    • new - 申请内存 - 默认值 - 初始值

2.1 Loading

  • 双亲委派

    • 父加载器
      - 父加载器不是“类加载器的加载器”,也不是“类加载器的父类加载器”
    • 双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程
    • 为什么要搞双亲委派
      - java.lang.String类由自定义类加载器加载行不行?不行,双亲委派,主要就是为了安全
      类加载器
      类加载过程
  • lazyloading

    • 严格讲应该叫lazyInitializing
    • JVM规范并没有规定何时加载
    • 但是严格规定了什么时候必须初始化,五种情况
      • new getstatic putstatic invokestatic 指令,访问final变量除外
      • java.lang.reflect对类进行反射调用时
      • 初始化子类的时候,父类首先初始化
      • 虚拟机启动时,被执行的主类必须初始化
      • 动态语言支持 java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic 的方法句柄时,该类必须初始化
  • 类加载器范围(来自Launcher源码)

    • sun.boot.class.path
      • Bootstrap ClassLoader加载路径
    • java.ext.dirs
      • ExtensionClassLoader加载路径
    • java.class.path
      • AppClassLoader加载路径
  • ClassLoader的源码

    • findCache -> parentLoadClass -> findClass
  • 自定义类加载器

    • 继承ClassLoader
    • 重写模板方法findClass
      • 调用defineClass
    • 自定义类加载器加载自加密的class
      • 防止反编译
      • 防止篡改
  • parent是如何指定的

    • 用super(parent)指定
  • 打破双亲委派

    • 如何打破:重写loadClass()
    • 何时打破过?
      • JDK1.2之前,自定义ClassLoader都必须重写loadClass()
      • ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
      • 热启动,热部署
        • osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
  • 混合模式

    • 解释器
      • bytecode intepreter
    • JIT
      • Just In-Time compiler
    • 混合模式
      • 混合使用解释器 + 热点代码编译
      • 起始阶段采用解释执行
      • 热点代码检测:-XX:CompileThreshold = 10000
        • 多次被调用的方法(方法计算器:监测方法执行频率)
        • 多次被调用的循环(循环计算器:检测循环执行频率)
        • 进行编译
    • JVM参数
      • -Xmixed 默认为混合模式,开始解释执行,启动速度较快,对热点代码实行检测和编译
      • -Xint 使用解释模式,启动很快,执行稍慢
      • -Xcomp 使用纯编译模式,执行很快,启动很慢

2.2 Linking

  • Verification
    • 验证文件是否符合JVM规定
  • Preparation
    • 静态成员变量赋默认值
  • Resolution
    • 将类、方法、属性等符号引用解析为直接引用
    • 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

2.3 Initializing

  • 调用类初始化代码

3. JMM - java memory model (java内存模型)

3.1 硬件层数据一致性

硬件层的并发优化基础知识
存储器的层次结构

协议有很多,Intel 用MESI

  • MESI Cache一致性协议

    • CPU每个cache line标记四种状态(额外两位)
    • 缓存锁实现之一,有些无法被缓存的数据,或者跨越多个缓存行的数据,依然必须使用总线锁
  • 现代CPU的数据一致性实现 = 缓存锁(MESI…) + 总线锁

  • 读取缓存以cache line为基本单位,目前64bytes

  • 位于同一缓存行的两个不同数据,被两个不同CPU锁定,产生互相影响的伪共享问题

  • 使用缓存行的对齐能够提高效率

3.2 乱序问题

  • CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据慢100倍 ),去同时执行另一条指令,前提是,两条指令没有依赖关系
  • 写操作也可以进行合并
  • 如何保证特定情况下不乱序?
    • 硬件内存屏障(x86),系统不同,实现不同
      - sfence: save | 在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
      - Ifence: load | 在Ifence指令前的读操作当必须在Ifence指令后的读操作前完成。
      - mfence: mix | 在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。

      原子指令,如x86上的"lock …"指令是一个Full Barrier, 执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序

    • JVM级别如何规范(JSR133)
      - LoadLoad屏障:
      对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
      - StoreStore屏障:
      对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
      - LoadStore屏障:
      对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
      - StoreLoad屏障:
      对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

    • volatile的实现细节

      • 字节码层面
      • JVM层面
      • OS和硬件层面
    • synchronized实现细节

      • 字节码层面
        ACC_SYNCHRONIZED
        monitorenter monitorexit
      • JVM层面
        C C++ 调用了操作系统提供的同步机制
      • OS和硬件层面
        X86 : lock comxchg xxxx

3.3 对象的创建过程

  1. class loading
  2. class linking(verification,preparation,resolution)
  3. class initializing
    1. 静态变量赋值
    2. 执行静态代码块
  4. 申请对象内存
  5. 成员变量赋默认值
  6. 调用构造方法
    1. 执行对象初始化代码块,成员变量顺序赋初始值
    2. 执行构造方法语句

3.4 对象标记字

  • 观察虚拟机配置
    java -XX:+PrintCommandLineFlags -version
  • 普通对象
  1. 对象头 markword 8
  2. ClassPointer指针:-XX:+UseCompressedClassPointers为4字节 不开启为8字节
  3. 实例
    – 引用类型: -XX:+UseCompressedOops 为4字节 不开启为8字节
    Oops Ordinary Object Pointers
    4. Padding对齐,8的倍数
  • 数组对象
  1. 对象头:markword 8

  2. ClassPointer指针同上

  3. 数组长度:4字节

  4. 数组数据

  5. 对齐 8的倍数

    markword,对象头具体包括什么?
    对象头
    分代年龄占位 4bit,4bit 的最大值为15,对象年龄最大值15就是这么来的

  • 对象定位
  1. 句柄池
  2. 直接指针
  • 对象怎么分配?
    对象分配流程

3.5 JVM Runtime Data Area and JVM Instructions - JVM运行时数据区与JVM指令集

3.5.1 指令集分类
  1. 基于寄存器的指令集
  2. 基于栈的指令集
    Hotspot中的Local Variable Table = JVM 中的寄存器
3.5.2 Runtime Data Area

运行时数据区
线程共享区域

3.5.2.1 pc - program counter 程序计数器

存放指令位置,虚拟机的运行,类似于这样的循环:

 while(not end){
    取PC中的位置,找到对应位置的指令;
    执行该指令;
    PC++}
3.5.2.2 JVM栈 - 栈帧 Frame栈帧
  1. Local Variable Table
  2. Operand Stack
    对于long的处理(store and load),多数虚拟机的实现都是原子的
    jls 17.7,没必要加 volatile
  3. Dynamic Linking
  4. Return address
    a() -> b(),方法a调用了方法b,b方法的返回值放在什么地方
3.5.2.3 Heap
3.5.2.4 Method Area
  1. Perm Space(<1.8)
    字符串常量位于Perm Space
    FGC不会清理
    大小启动的时候指定,不能变
  2. Meta Space(>=1.8)
    字符串常量位于堆
    会触发FGC清理
    不设定的话,最大就是物理内存
  3. Runtime Constant Pool
3.5.2.5 Native Method Stack
3.5.2.6 Direct Memory
  1. JVM可以直接访问的内核空间的内存(OS管理的内存)
  2. NIO,提高效率,实现zero copy

4. JVM常用指令

  1. store
  2. load
  3. pop
  4. mul
  5. invoke
    1. InvokeStatic
    2. InvokeVirtual
    3. InvokeInterface
    4. InvokeSpecial - 可以直接定位,不需要多态的方法
    private方法、构造方法
    5. InvokeDynamic - JVM最难的指令
    lambda表达式、反射、其他动态语言scala kotlin、CGLIB ASM 、动态产生的class,会用到的指令

5. Garbage Collector

5.1 GC tuning

目标:
熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验

  • 什么是垃圾?
    没有任何引用指向的一个对象或者多个对象(循环引用)

  • 如何定位垃圾?

  1. 引用计数 - 无法解决循环引用问题
  2. 根可达算法 - 哪些是根对象?
    JVM stack , native method stack , run-time constant pool , static references in method area , clazz
  • 常见的垃圾回收算法
  1. 标记清除(Mark Clear)- 位置不连续,产生碎片,效率偏低(两遍扫描)
  2. 拷贝算法(Copy) - 没有碎片,浪费空间
  3. 标记压缩(Mark Compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)

5.2 JVM内存分代模型

堆内存逻辑分区
GC

  • JVM分代算法
  1. New - young
    • 存活对象少
    • 使用copy算法,效率高
  2. old
    • 垃圾少
    • 一般使用mark compact
    • g1使用copy

(用于分代垃圾回收算法)

  1. 部分垃圾回收器使用的模型
    除 Epsilon ZGC Shenandoah 之外的GC都是使用逻辑分代模型
    G1是逻辑分代,物理不分代
    除此之外不仅逻辑分代,而且物理分代
  2. 新生代 + 老年代 + 永久代(1.7)Permanent Generation / 元数据区(1.8) Metaspace
    1. 永久代、元数据 - Class
    2. 永久代必须制定大小限制,元数据可以设置,也可以不设置,无上限(受限于物理内存)
    3. 字符串常量 1.7 - 永久代 , 1.8 - 堆
    4. MethodArea 逻辑概念 - 永久代、元数据
  3. 新生代 = Eden + 2个 survivor 区
    1. YGC回收之后,大多数的对象会被回收,活着的进入s0
    2. 再次YGC,活着的对象eden + s0 -> s1
    3. 再次YGC,eden + s1 -> s0
    4. 年龄足够 -> 老年代 (15 / CMS 6)
    5. s区装不下 -> 老年代
    6. s0 - s1 之间的复制年龄超过限制时,进入old区,通过参数:-XX:MaxTenuringThreshold 配置,最大值15
  4. 老年代
    1. 顽固分子
    2. 老年代满了触发FGC - Full GC
  5. GC Tuning(Generation)
    1. 尽量减少FGC
    2. MinorGC = YGC
    3. MajorGC = FGC
  • 栈上分配
  1. 线程私有小对象
  2. 无逃逸
  3. 支持标量替换
  4. 无需调整
  • 线程本地分配TLAB(Thread Local Allocation Buffer)
  1. 占用eden,默认1%
  2. 多线程的时候不用竞争eden就可以申请空间,提高效率
  3. 小对象
  4. 无需调整
  • 老年代 - 大对象
  • eden

对象何时进入老年代?

  • 超过 XX:MaxTenuringThreshold 指定次数(YGC)
    - Parallel Scavenge 15
    - CMS 6
    - G1 15
  • 动态年龄(不重要)
    - eden + s1 -> s2 超过50%
    - 把年龄最大的放入O
    https://www.jianshu.com/p/989d3b06a49d
  • 分配担保(不重要)
    YGC期间 survivor区空间不够了,空间担保直接进入老年代

5.3 常见的垃圾回收器

常见的垃圾回收器

  1. JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认都不是CMS
    并发垃圾回收是因为无法忍受STW
  2. Serial - 年轻代 串行回收
  3. PS - 年轻代 并行回收
  4. ParNew - 年轻代 配合CMS的并行回收
  5. SerialOld - 老年代 串行回收
  6. ParallelOld - 老年代 并行回收
  7. Concurrent Mark Sweep - 老年代 并发的,垃圾回收和应用程序同时运行,降低STW(stop the world)的时间(缩短至200ms以内)。
    CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定。
    CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收。
    想象一下:
    PS + PO ->加内存,换垃圾回收器-> PN + CMS + SerialOld(几个小时 - 几天的STW)
    几十个G的内存,单线程回收-> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
    算法:三色标记 + Incremental Update
  8. G1 - 10ms
    算法:三色标记 + SATB
  9. ZGC - 1ms PK C++
    算法:ColoredPointer + 读屏障
  10. Shenandoah
    算法:ColoredPointer + 读屏障
  11. Epsilon
  12. PS 和 PN的区别
  13. 垃圾收集器跟内存大小的关系
    1. Serial 几十兆
    2. PS 上百兆 - 几个G
    3. CMS - 20G
    4. G1 - 上百G
    5. ZGC - 4T ~ 16T (JDK 13)

1.8默认的垃圾回收器: PS + ParallelOld

5.3.1 CMS
  • Concurrent mark sweep
  • A mostly concurrent , low-pause collector
  • 4 phases
    1. initial mark
    2. concurrent mark
    3. remark
    4. concurrent sweep

cms

CMS的问题

  1. Memory Fragmentation
    -XX:+UseCMSCompactAtFullCollection
    -XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩
  2. Floating Garbage
    Concurrent Mode Failure 产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
    解决方案:降低触发CMS的阈值
    PromotionFailed
    解决方案类似,保持老年代有足够的空间
    –XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

5.4 JVM调优第一步,了解生产环境下的垃圾回收器组合

  • JVM的命令行参数参考: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
  • HotSpot参数分类
    标准: - 开头,所有的HotSpot都支持
    非标准: -X 开头,特定版本HotSpot支持特定命令
    不稳定: -XX 开头,下个版本可能取消

Java -version
Java -X

  1. 区分概念:内存泄漏memory leak,内存溢出out of memory
  2. java -XX:+PrintCommandLineFlags HelloGC
  3. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
    PrintGCDetails PrintGCTimeStamps PrintGCCauses
  4. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
  5. java -XX:+PrintFlagsInitial 默认参数值
  6. java -XX:+PrintFlagsFinal 最终参数值
  7. java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
  8. java -XX:+PrintFlagsFinal -version |grep GC

5.5 常见垃圾回收器组合参数设定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
    • 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
  • -XX:+UseParNewGC = ParNew + SerialOld
    • 这个组合已经很少用(在某些版本中已经废弃)
    • https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
  • -XX:+UseConcurrentMarkSweepGC = ParNew + CMS + Serial Old
  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
  • -XX:+UseG1GC = G1
  • Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
    • java +XX:+PrintCommandLineFlags -version
    • 通过GC的日志来分辨
  • Linux下1.8版本默认的垃圾回收器到底是什么?
    • 1.8.0_181 默认(看不出来)Copy MarkCompact
    • 1.8.0_222 默认 PS + PO

5.6 解决JVM运行中的问题

  • java -Xms200M -Xmx200M -XX:+PrintGC HelloGC
  • 运维团队首先收到报警信息(CPU Memory)
  • top命令观察到问题:内存不断增长 CPU占用率居高不下
  • top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
  • jps定位具体java进程
    jstack 定位线程状况,重点关注:WAITING BLOCKED
    eg.
    waiting on <0x0000000088ca3310> (a java.lang.Object)
    假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁
    怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE
  • 为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称
    怎么样自定义线程池里的线程名称?(自定义ThreadFactory)
  • jinfo pid
  • jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
    jstat -gc 4655 500 : 每隔500毫秒打印GC的情况
  • jmap - histo 4655 | head -20,查找4655进程有多少对象产生
  • jmap -dump:format=b,file=xxx pid :
    线上系统,内存特别大,jmap -dump执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
    1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
    2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
    3:在线定位(一般小点儿公司用不到)
  • java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
  • 使用MAT / jhat /jvisualvm 进行dump文件分析
    https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
    jhat -J-mx512M xxx.dump
    http://192.168.17.11:7000
    拉到最后:找到对应链接
    可以使用OQL查找特定问题对象
  • 找到代码的问题

6. GC常用参数

  • -Xmn -Xms -Xmx -Xss年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB使用TLAB,默认打开
  • -XX:+PrintTLAB打印TLAB的使用情况
  • -XX:TLABSize设置TLAB大小
  • -XX:+DisableExplictGCSystem.gc()不管用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低)打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime (低)打印暂停时长
  • -XX:+PrintReferenceGC (重要性低)记录回收了多少种不同引用类型的引用
  • -verbose:class类加载详细过程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial必须会用
  • -Xloggc:opt/log/gc.log
  • -XX:MaxTenuringThreshold升代年龄,最大值15
  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 … 这些不建议设置

6.1 Parallel常用参数

  • -XX:SurvivorRatio
  • -XX:PreTenureSizeThreshold大对象到底多大
  • -XX:MaxTenuringThreshold
  • -XX:+ParallelGCThreads并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy自动选择各区大小比例

6.2 CMS常用参数

  • -XX:+UseConcMarkSweepGC
  • -XX:ParallelCMSThreadsCMS线程数量
  • -XX:CMSInitiatingOccupancyFraction使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction达到什么比例时进行Perm回收
  • GCTimeRatio设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

6.3 G1常用参数

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis?GC的间隔时间
  • -XX:+G1HeapRegionSize分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长ZGC做了改进(动态区块大小)
  • G1NewSizePercent新生代最小比例,默认为5%
  • G1MaxNewSizePercent新生代最大比例,默认为60%
  • GCTimeRatioGC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads线程数量
  • InitiatingHeapOccupancyPercent启动G1的堆空间占用比例
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值