架构师对JVM的理解

59 阅读5分钟

JVM.png

Class File

语法分析 -> 语法分析器 -> 抽象语法树 -> 语义分析器 -> 注解 -> 字节码生成器

javac HelloWork.class -> class文件
javap -v -c -p HelloWork.class -> 反编译生成 HelloWork.txt

类加载机制

  • 装载 Loading
    • Bootstrap ClassLoader:加载jdk包 jre/lib/rt.jar 所有的class
    • Extension ClassLoader:加载扩展包 jre/lib/*.jar
    • App ClassLoader:加载classpath中指定的jar
    • Custom ClassLoader:加载java.lang.ClassLoader的子类自定义的class
  • 链接 Linking
    • (检验)保证类加载的正确性:格式校验,语义校验,字节码校验,符号引用校验
    • (准备)类的静态变量分配内存,并初始化默认值
    • (解析)类的符号引用转化为直接引用(逻辑地址转为物理地址)
  • 初始化 Initialization
    • 对类的静态变量,静态代码块初始化默认值
  • 使用 Using
  • 卸载 Unloading

双亲委派机制

自底向上,从custom到bootStrap逐层检查,只要某个ClassLoader已加载,就认为加载此类,保证只加载一次。自顶向下,也就是由上层逐层尝试加载此类。

  • 为什么要设计双亲委派机制
    • 沙箱安全机制
    • 避免重复加载
  • 如何破坏双亲委派机制
    • tomcat 自定义类加载器
    • SPI机制
    • OSGI

JVM 架构

image.png

运行时数据区

  • Method Area 方法区 (非堆)
    • 存储类信息,常量,静态变量,编译后的代码等元数据,类的定义
    • String 常量存在哪里?
      • JDK1.7存方法区
      • JDK1.8存堆中
  • Heap(堆)
    • 虚拟机启动时创建,被所有线程共享
    • java对象实例化及数组都在堆上分配
  • Java Virtual Machine Stacks(Java 虚拟机栈)
    • 线程执行的区域,保存着一个线程中方法的调用状态。
    • 线程私有的,独立的,随着线程的创建而创建
    • 每一个被线程执行的方法,栈中称为栈帧,也就是每个方法对应一个栈帧
    • 调用一个方法,就会向栈中压入一个栈帧,一个方法调用完成,就会把栈帧从栈中弹出
  • Native Method Stacks(本地方法栈)
    • java调用的Native方法
  • The PC Register(程序计数器)
    • 线程执行java方法,计数器记录正在执行的虚拟机字节码指令地址,如果时Native方法,计数器为空。

image.png

JVM内存模型

  • 线程私有:虚拟机栈,本地方法栈,程序计数器
  • 线程共享:方法区(非堆)和堆
  • 新对象申请内存空间
    • Eden是否有足够空间?
    • (MinorGC)-> JVM回收不活跃对象
      • Eden是否有足够空间?
        • Survivor区是否有空间?
        • Eden区活跃对象复制到Survivor区
          • Old区是否有足够空间?
          • 将Survivor区活跃对象复制到Old区
          • (FullGC)-> JVM回收不活跃对象
            • OutofMemeoryError

image.png

垃圾收集

  • 确认垃圾对象
    • 引用计数法:对象没有任何指针对其引用,它就是垃圾。(存在互相引用场景)
    • 可达性分析法:通过GC Root对象,往下寻找,看某个对象是否可达。
  • 垃圾收集算法
    • 标记清除:先标记需要回收的对象,直接清除掉,释放空间(Old)
      • 缺点:效率不高,存在内存碎片
    • 标记整理:先标记,再将标记的移动到相邻的空间中(Old)
      • 优点:内存空间连续,缺点的,耗费两个区域空间(S0,S1)
    • 标记复制:先标记,再复制。(young)
      • 缺点:大量复制操作,效率降低,空间利用率降低
  • 垃圾收集器
    • Serial:串行收集器
    • Parallel:并行收集器(关注于吞吐量的收集器)(类似Serial的多线程模式)
    • CMS:并发收集器,低停顿用于Old
      • 初始标记:GC Root直接关联
      • 并发标记:GC Root追踪关联对象
      • 重新标记:并发标记变动内容(STW)
      • 并发清除:清除不可达对象回收空间
    • G1:也是并发收集器,更关注停顿时间(和可配置的停顿时间)
      • 初始标记:GC Roots标记对象(停止用户线程可自定义停顿时间)
      • 并发标记:GC Root追踪关联对象
      • 最终标记:修正标记变动内容(STW)
      • 筛选回收:Region回收价值成本排序,根据期望停顿时间制定回收策略
    • ZGC:jdk11,10ms停顿时间要求,支持TB级别,64位操作系统使用

JVM 参数

官方文档:docs.oracle.com/javase/8/do…

  • Boolean
    • -XX:+UseConcMarkSweepGC
    • -XX:+UseG1GC
  • 非Boolean
    • -XX:MaxGCPauseMillis=500
    • -Xms1000M 等价于 -XX:InitialHeapSize=1000M
    • -Xmx1000M 等价于 -XX:MaxHeapSize=1000M
    • -Xss100 等价于 -XX:ThreadStackSize=100
  • 查看参数
    • 启动时添加 +PrintFlagsFinal
    • 通过jinfo查看
  • 参数建议优化
    • ‐XX:MaxTenuringThreshold:自适应GC大小,默认值15,CMS为6
    • ‐XX:PretenureSizeThreshold:超过多大的对象直接在老年代分配
    • ‐XX:+/‐ UseAdaptiveSizePolicy:是否启动自适应生成大小调整(默认启用)
    • ‐XX:SurvivorRatio:S区比例默认为8
    • ‐XX:ConcGCThreads:并发GC的线程数(默认cpu数量)
    • ‐Xsssize:线程堆栈大小
    • ‐Xms和‐Xmx :初始堆和最大堆。两者值一样,防止动态扩容
    • ‐XX:ReservedCodeCacheSize:JIT编译空间缓存大小
  • G1调优建议
    • 不要手动设置新生代和老年代大小,只要设置整体的堆大小
    • 不断调优停顿时间为目标
    • 使用 -XX:ConcGCThreads=n 来增加标记线程的数量
    • 适当增加堆内存大小
    • 每一次 FULL GC 都需要关注原因

image.png

JVM 命令

官方文档:docs.oracle.com/javase/8/do…

  • jps
  • jinfo
    • jinfo -flag name PID
    • jinfo -glags PID
  • jstat
    • jstat -class PID 1000 10 查看类加载信息
    • jstat -gc PID 1000 10 查看GC信息
  • jstack
  • jmap
    • jmap -heap PID
    • jmap -dump:format=xx,file=heap.hprof PID

分析工具