JVM

yxalkaid

JVM

  • JVM内存模型

    • 程序计数器
    • 虚拟机栈
    • 本地方法栈
    • 元空间:存储加载的类信息、常量、静态变量、JIT编译后的代码缓存等
      • 运行时常量池
  • Java堆
    线程共享区域,主要用来保存对象和数组。

    • 年轻代
    • 老年代
      jdk1.7的堆区有一个永久代,存储的是类信息、静态变量、常量、JIT编译后的代码缓存等。
      jdk1.8移除了永久代,改用本地内存区的元空间,避免内存溢出。
  • 虚拟机栈
    每个线程运行时所需要的内存。默认栈内存为1M。
    方法内的局部变量是线程安全的。

  • 方法区
    方法区是各个线程共享的内存区域,存储的是类信息、运行时常量池。
    虚拟机启动时创建,关闭时释放。

    • 常量池:类名、方法名、参数类型、字面量等
    • 运行时常量池:当类被加载时,常量池信息会被放入运行时常量池中,并将符号地址变为内存地址。
  • 直接内存
    不属于JVM的内存,不由JVM管理,是虚拟机的系统内存。
    常见于NIO操作时,用于数据缓冲区。

  • 类加载器
    将字节码文件加载到JVM中。

    • 启动类加载器:加载jre/lib下的库
    • 扩展类加载器:加载jre/lib/ext下的类
    • 应用程序类加载器:加载classpath下的类
    • 自定义类加载器
  • 双亲委派模型

    • 加载类时,先委托上级加载器加载,如果找不到,则由子加载器加载。
    • 避免类被重复加载,保证类库API不会被修改。
  • 类装载的执行过程

    • 加载:将字节码文件加载到JVM中。
    • 验证:验证字节码文件的正确性。
    • 准备:为类变量分配内存,并赋予初始值。
    • 解析:将符号引用转换为直接引用。
    • 初始化:执行类的静态代码块和静态变量的初始化。
    • 使用:JVM从入口方法中开始执行。
    • 卸载:当类不再被使用时,卸载掉。
  • 对象创建的过程

    • 类加载检查
    • 分配内存
    • 初始化零值
    • 进行必要设置(对象头等)
    • 执行构造方法
  • 垃圾回收

    • 引用计数法
    • 可达性分析:沿GC Root对象为起点的引用链找不到的对象则可以被回收。
      • 虚拟机栈中引用的对象
      • 类静态属性引用的对象
      • 常量引用的对象
      • 本地方法栈中引用的对象
  • 垃圾回收算法

    • 标记清除算法
      • 标记存活对象,清除无用对象。
      • 效率高,内存碎片多。
    • 标记整理算法
      • 对象需要移动,效率低
    • 复制算法
      • 两块内存,每次只使用其中一块。
      • 内存利用率低
  • 分代回收
    年轻代和老年代
    - 年轻代:三分之一
    - Eden:0.8
    - from:0.1
    - to:0.1
    - 老年代:三分之二
    工作机制
    - 新创建的对象放到Eden中。
    - Eden区内存不足时,标记Eden和from中的存活对象,将其移动到to,然后释放Eden和from区。
    - 幸存对象经过多次回收(最多15次)后,移到老年代区。
    - 幸存区内存不足或大对象,会提前移到老年代区。
    GC
    - MinorGC(youngGC):仅新生代,暂停时间短
    - MixedGC:新生代和部分老年代,G1收集器特有
    - FullGC:全部新生代和老年代,暂停时间长

  • 垃圾回收器

    • 串行垃圾收集器:只有一个线程工作,适合堆内存小的情况
      • Serial:采用复制算法,作用于新生代
      • SerialOld:采用标记整理算法,作用于老年代
    • 并行垃圾收集器(jdk8默认):多个线程共同工作
      • ParNew:采用复制算法,作用于新生代
      • ParallelOld:采用标记整理算法,作用于老年代
    • CMS垃圾收集器(并发):采用标记清除算法,针对老年代
      • 初始标记(需要暂停)
      • 并发标记
      • 重新标记(需要暂停)
      • 并发清除
    • G1垃圾收集器
  • G1垃圾收集器(jdk9后默认)
    响应时间和吞吐量兼顾,摒弃了传统的物理分区方式
    可预测的停顿时间
    划分为多个区域,每个区域都可充当Eden、Survivor、Old、humongous(为大对象准备)
    维护一个Collect Set集合。这个集合记录了待回收的Region块的信息
    当创建新对象速度快于回收速度,将会触发FullGC
    采用复制算法,分三个阶段:

    • 新生代回收
      • 创建对象时,选择空闲区域作为Eden区进行存储。
      • 当Eden内存不足时,挑选一个空闲区域作为Survivor区,进行回收。(暂停用户线程)
      • 当Eden内存再次不足时,对Eden区与Survivor区进行回收。移动到新Survivor区,其中较老的对象移动到Old区。
    • 并发标记
      • 当老年代占用内存超过阈值(默认45%),触发并发标记(不暂停用户线程)。
      • 重新标记,需要暂停用户线程。
    • 混合收集
      • 对Eden区与Survivor区进行回收,移动到新Survivor区
      • 优先回收价值高的区域,目标Old区复制到新Old区
  • 引用的类别

    • 强引用
      • 只有所有GC root都不通过强引用引用某对象时,该对象才会被回收。
    • 软引用:使用SoftReference类
      • 某对象仅被软引用时,在垃圾回收后,内存仍不足时,该对象会被回收。
    • 弱引用:使用WeakReference类
      • 某对象仅被弱引用时,在垃圾回收时,不论内存是否充足,该对象都会被回收。
    • 虚引用:必须配合ReferenceQueue使用
      • 被引用的对象回收时,虚引用对象会被加入到ReferenceQueue中,由ReferenceHandler线程处理。
  • JVM调优的参数

    • 设置堆空间的大小
      • 初始大小:默认为物理内存的1/64
      • 最大大小:默认为物理内存的1/4
    • 虚拟机栈的设置
      • 默认为1M
    • Eden和Survivor区的比例
      • 默认为8:1:1
    • 晋升阈值
      • 默认为15,可取范围为0-15
    • 垃圾回收器
  • JVM调优工具

    • jps
    • jstack
    • jmap
  • CPU占用高的排除

    • top:查看占用CPU高的进程。
    • ps:查看指定进程中的线程占用CPU的信息。
    • jstack:定位问题
On this page
JVM