# JVM学习

参照《深入理解Java虚拟机》一书,对Java虚拟机进行学习。

# JVM运行时数据区域

JVM运行时数据区域有堆内存区,虚拟机栈,本地方法栈,程序计数器,为元空间以及直接内存。如下图:

    1)程序计数器
    程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器(当前线程栈区当前栈帧的执行位置,也就是当前线程执行到了方法的字节码的哪一行)。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    程序计数器占用内存非常小,小到可以忽略不计。此外,这块内存区域也是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
    2)虚拟机栈
    虚拟机栈描述的是Java方法执行的线程内存模型:每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。该区域是线程私有的,一般会分配1M左右的内存空间。
    3)本地方法栈
    本地方法栈时当前线程执行本地方法(native方法)的一个栈区(一般分配1M左右的内存),原则上和虚拟机栈没有任何差异,区别仅在于这部分特殊方法仅能通过c++/c实现。因此这部分方法要单独用一个栈区存储。
    4)元空间
    元空间即元数据(包括类的结构信息、方法信息、字段信息、注解信息等)存储的空间,此外元空间中还包含了运行时常量池。
    5)直接内存
    直接内存指的是直接通过操作系统获取的内存。在Java中,只有在NIO相关操作中JVM才会直接通过操作系统获取内存。在64位的操作系统中,直接内存的默认大小通常为1GB。可以通过-XX:MaxDirectMemorySize参数来设置JVM直接内存的最大大小。如果没有显式指定该参数,默认值将是JVM的最大可用内存大小(即-Xmx参数指定的值)。
    6)堆内存区

    堆内存区由新生代及老年代组成,其中新生代区又分为Eden区以及Survived区(2个,对应S0区以及S1区,也即From区以及To区)。
    堆区存储的是对象本身,即一个很特殊的数据结构。可以根据这个数据结构+类的源码构建出一个对象。

# 是否在线程中,一个方法的执行就对应一个栈帧?本地方法和普通方法是否作为栈帧分别存储在不同的栈上?

是的,一个方法的执行就对应一个栈帧的入栈,普通方法的执行和本地方法的执行对应不同栈的栈帧入栈,普通方法会进入Java虚拟机栈,本地方法会进入本地方法栈。

# 如何在虚拟机栈和本地方法栈之间切换?

虚拟机栈执行到本地方法栈之后,就会压入一个栈帧到本地方法栈,显然这是一个特殊栈帧,极端情况,这个栈帧(方法)不调用其他方法,那么这个栈帧执行完毕后,就会恢复到虚拟机栈的当前栈帧的具体恢复点来执行代码。

# 程序计数器记的是什么?

程序计数器记录的是当前栈帧中的代码执行点,也就是具体执行到了方法的哪个指令。如果当前栈帧执行到本地方法调用,那么就会把程序计数器的值作为本地方法栈的一个参数压入本地方法栈,此时程序计时器的值会修改为undefined,当本地方法栈执行完成出栈时,又会把之前保存的程序计数器的值还原给程序计数器(从undefined改成计数器的值),线程在当前虚拟机栈的栈帧的恢复点继续执行。

# 虚拟机栈的示意图


# 什么是OutOfMemory?

OOM全称“Out Of Memory”,表示内存耗尽。当JVM因为没有足够的内存来为对象分配空间,并且垃圾回收器也已经没有空间可回收时,就会抛出这个错误。需要注意的是,堆区,栈区,本地内存(元空间,直接内存)这几个区域都有可能发生OOM。

# 堆区内存溢出OOM

要通过代码模拟堆区的OOM异常,我们只需要将堆内存的最大值调小,同时一直创建对象就可以了,创建的对象最初会存储在新生代,在多次YoungGC后这部分没有被清理的对象会被存储到老年代,最终新生代,老年代都会被填满,然后在FullGC的时候,内存回收率小于2%,触发OOM。
编写如下代码进行验证。

查看代码
package com.howl.jvm;

import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
    static class OOMObject{
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}
如何查看溢出对象的GC Root?




设置JVM启动相关参数,如图所示。

执行代码,查看运行结果,可以看到日志中的报错以及生成的hprof文件如图。


接下来我们通过JProfiler工具分析报错的hprof文件。
首先查看Thread Dump,我们可以很明显的看到,此时jvm中的线程并不多,也可以看到有问题的是main线程,它触发了OOM。
接下来查看Current Object Set,我们可以看到此时系统里创建了82W的对象,其中81W是OOMObject,结合Thread Dump,我们很容易就能联想到main线程里创建OOMObject的相关代码可能有问题(对应的就是while循环的代码)。

# 栈区内存溢出OOM

在JVM中,虚拟机栈和本地方法栈没有区别,HotSpot中,栈区只会抛出StackOverFlowError,有两种方式可以模拟这个异常,一个是方法递归(一直增加栈帧),另外一个是填充非常多的变量到局部变量表。两者的最终目的都是用数据填充满栈空间。
这里我们通过增大方法栈中的本地变量表长度的方式,编写如下代码进行验证。

查看代码
package com.howl.jvm;

public class JavaVMStackSOF {
    private static int stackLength = 0;
    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
                unused6, unused7, unused8, unused9, unused10,
                unused11, unused12, unused13, unused14, unused15,
                unused16, unused17, unused18, unused19, unused20,
                unused21, unused22, unused23, unused24, unused25,
                unused26, unused27, unused28, unused29, unused30,
                unused31, unused32, unused33, unused34, unused35,
                unused36, unused37, unused38, unused39, unused40,
                unused41, unused42, unused43, unused44, unused45,
                unused46, unused47, unused48, unused49, unused50,
                unused51, unused52, unused53, unused54, unused55,
                unused56, unused57, unused58, unused59, unused60,
                unused61, unused62, unused63, unused64, unused65,
                unused66, unused67, unused68, unused69, unused70,
                unused71, unused72, unused73, unused74, unused75,
                unused76, unused77, unused78, unused79, unused80,
                unused81, unused82, unused83, unused84, unused85,
                unused86, unused87, unused88, unused89, unused90,
                unused91, unused92, unused93, unused94, unused95,
                unused96, unused97, unused98, unused99, unused100;
        stackLength ++;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 =
                unused6 = unused7 = unused8 = unused9 = unused10 =
                        unused11 = unused12 = unused13 = unused14 = unused15 =
                                unused16 = unused17 = unused18 = unused19 = unused20 =
                                        unused21 = unused22 = unused23 = unused24 = unused25 =
                                                unused26 = unused27 = unused28 = unused29 = unused30 =
                                                        unused31 = unused32 = unused33 = unused34 = unused35 =
                                                                unused36 = unused37 = unused38 = unused39 = unused40 =
                                                                        unused41 = unused42 = unused43 = unused44 = unused45 =
                                                                                unused46 = unused47 = unused48 = unused49 = unused50 =
                                                                                        unused51 = unused52 = unused53 = unused54 = unused55 =
                                                                                                unused56 = unused57 = unused58 = unused59 = unused60 =
                                                                                                        unused61 = unused62 = unused63 = unused64 = unused65 =
                                                                                                                unused66 = unused67 = unused68 = unused69 = unused70 =
                                                                                                                        unused71 = unused72 = unused73 = unused74 = unused75 =
                                                                                                                                unused76 = unused77 = unused78 = unused79 = unused80 =
                                                                                                                                        unused81 = unused82 = unused83 = unused84 = unused85 =
                                                                                                                                                unused86 = unused87 = unused88 = unused89 = unused90 =
                                                                                                                                                        unused91 = unused92 = unused93 = unused94 = unused95 =
                                                                                                                                                                unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }
    public static void main(String[] args) {
        try {
            test();
        }catch (Error e){
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

执行代码,查看运行结果,可以看到控制台的报错信息如下。

# 线程创建过多导致OOM

在java里创建一个新线程时,会为这个线程分配栈区内存空间,默认1M,所以创建线程的动作本身也是会消耗内存的(栈空间的内存应该是直接用的本地内存)。如果线程创建过多,操作系统内存不足,一样会触发OOM。
这里我们在while循环中一直创建线程的方式来模拟,编写如下代码进行验证。

查看代码
package com.howl.jvm;

/**
 * VM Args:-Xss2M
 * 代码没办法验证,windows下直接操作系统假死也没有报错
 * 在32位windows操作系统下会报异常
 * Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
 */
public class JavaVMStackOOM {
    // 线程一直在工作
    private void dontStop() {
        while (true) {
        }
    }
    public void stackLeakByThread() {
        // 一直创建线程
        while (true) {
            Thread thread = new Thread(this::dontStop);
            thread.start();
        }
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

# 元空间溢出OOM

方法区中存放的是类的数据结构,只要不断往方法区中加入新的类,就会产生方法区的溢出,可以使用类加载器不断加载类或者动态代理不断生成类来演示。
这里使用的是JDK8,方法区的具体实现为元空间,也就是说下面的代码演示的是元空间的溢出。

查看代码
package com.howl.jvm;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示元空间OOM
 * VM args:-XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=16m
 */
public class MetaSpaceOOM {

    public static void main(String[] args) {

        List<ClassLoader> classLoaderList = new ArrayList<>();
        while (true) {
            ClassLoader loader = new URLClassLoader(new URL[]{});
            Facade t = (Facade) Proxy.newProxyInstance(loader, new Class<?>[]{Facade.class}, new MetaspaceFacadeInvocationHandler(new FacadeImpl()));
            classLoaderList.add(loader);
        }
    }

    public interface Facade {
    }

    public static class FacadeImpl implements Facade {
    }

    public static class MetaspaceFacadeInvocationHandler implements InvocationHandler, com.howl.jvm.MetaspaceFacadeInvocationHandler {
        private Object impl;

        public MetaspaceFacadeInvocationHandler(Object impl) {
            this.impl = impl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(impl, args);
        }
    }
}

可以很明显的看到报错提示如下图。

# 直接内存溢出OOM

直接内存的默认大小和堆的最大值-Xmx一致,直接内存溢出的唯一条件是直接或间接使用了NIO。直接内存导致OOM时,仅会在日志中看到Exception in thread "main" java.lang.OutOfMemoryError,除此之外再无其他提示。

查看代码
/**
 * 代码来自《深入理解Java虚拟机》一书,这里并未进行验证
 */
package com.howl.jvm;
/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 * @author zzm
 */
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}

# JVM启动参数设置

# 初始内存分配

# 设置堆内存大小 推荐

堆内存大小可以通过-Xms(最小堆内存,默认值是物理内存的1/64)参数和-Xmx(最大堆内存,默认是物理内存的1/4)参数进行设置,一般我们会将这两个参数设置成一样的大小(可以避免垃圾回收后JVM重新分配内存),设置为物理内存的75%的大小。

堆内存里其他分区的设置

1.可以通过-Xmn设置新生代的大小,新生代大小占堆内存的3/8(JDK官方建议)。
2.新生代又分为Eden以及Survivor(两个),一般Eden区占新生代的80%,两个Survivor各占10%(新生代2个Survivor区和Eden区的比值,默认值为8;即Eden区:From区:To区 = 8: 1:1),可以通过-XX:SurvivorRatio参数来调整这个大小,一般不调整。
3.可以通过NewRatio设置新生代(Eden + 2*Survivor)与老年代(不包括永久区)的比值,JDK8的默认值为2(可以看到这个值与-Xmn冲突,一般-Xmn优先级更高)。

# 设置栈内存大小

默认的栈内存大小为1024k(1M),不建议修改(通过-Xss参数可以设置每个线程对应的栈内存的大小)。

# 设置元空间内存大小 推荐

元空间内存大小可以通过-XX:MetaspaceSize以及-XX:MaxMetaspaceSize两个参数进行设置。一般会将这两个参数设置成相同的值。(实际项目里会设置为128m或者512m)

详细说明

使用Java 8以后,关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始大小是20.75MB,默认的元空间的最大值是无限。MaxMetaspaceSize用于设置metaspace区域的最大值,这个值可以通过mxbean中的MemoryPoolBean获取到,如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大。

# 设置直接内存大小

直接内存(DirectMemory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致(实际上直接内存的默认值大小为Eden+(From+To)/2+Old)。

# GC相关参数设置

# 设置GC并发线程数

GC并发线程数可以通过JVM启动参数:-XX:ParallelGCThreads来指定。一般来说,在无特殊要求下,ParallelGCThreads参数使用默认值就可以了。在未明确指定的情况下,JVM会根据逻辑核数ncpus,采用以下公式来计算默认值:
    1) 当ncpus小于8时,ParallelGCThreads = ncpus
    2) 否则 ParallelGCThreads = 8 + (ncpus - 8 ) ( 5/8 )

# 指定垃圾回收器

可以通过参数指定垃圾回收器的使用,一般有如下参数可供选择:
    1)-XX:+UseSerialGC
    对新生代使用Serial算法进行垃圾回收,对老年代使用Serial Old算法进行垃圾回收。
使用串行回收器进行回收,这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法。Serial收集器是最基本、历史最悠久的收集器,它是一个单线程收集器。一旦回收器开始运行时,整个系统都要停止。Client模式下默认开启,其他模式默认关闭。
    2) -XX:+UseParNewGC
    对新生代使用ParNew算法进行垃圾回收,对老年代使用Serial Old算法进行垃圾回收。
ParNew收集器是Serial收集器的多线程版本,使用这个参数后会在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法。操作系统是多核CPU上效果明显,单核CPU建议使用串行回收器。打印GC详情时ParNew标识着使用了ParNewGC回收器。默认关闭。
    3) -XX:+UseConcMarkSweepGC
    对新生代使用ParNew算法进行垃圾回收,对老年代使用CMS算法进行垃圾回收。
    4)-XX:+UseParallelGC jdk8默认垃圾回收器
    对新生代使用Parallel Scavenge算法进行垃圾回收,对老年代使用Parallel Old算法进行垃圾回收。
代表新生代使用Parallel收集器,老年代使用串行收集器(UseSerialGC)。Parallel Scavenge收集器在各个方面都很类似ParNew收集器,它的目的是达到一个可以控制的吞吐量。吞吐量为CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机运行100分钟,垃圾收集花费1分钟,那吞吐量就99%。Server模式默认开启,其他模式默认关闭。
    5) -XX:+UseG1GC jdk9及以上默认垃圾回收器
    G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器,分区既可以是年轻代也可以是老年代,同一个代的分区不需要连续。并且每个代分区的数量是可以动态调整的。为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的分区,这也是G1名称的由来。不过这个算法并不适合新生代垃圾收集,因为新生代的垃圾收集算法是复制算法,但是新生代也使用了分区机制主要是因为便于代大小的调整。

# GC日志记录

GC日志记录相关参数可以在触发Young GC以及Full GC的时候打印相关参数,一般情况下不会设置这部分参数(仅在需要分析GC问题时考虑添加参数,GC问题处理后需要移除)。

详细的GC日志参数
# 必选
# 打印基本 GC 信息
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
# 打印对象分布
-XX:+PrintTenuringDistribution
# 打印堆数据
-XX:+PrintHeapAtGC
# 打印Reference处理信息
# 强引用/弱引用/软引用/虚引用/finalize 相关的方法
-XX:+PrintReferenceGC
# 打印STW时间
-XX:+PrintGCApplicationStoppedTime

# 可选
# 打印safepoint信息,进入 STW 阶段之前,需要要找到一个合适的 safepoint
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1

# GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation
# 最多分割几个文件,超过之后从头文件开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=50M

# OOM相关参数设置 重要

与OOM有关的JVM启动参数有3个,分别是HeapDumpOnOutOfMemoryError(在OOM时生成heapdump堆转储文件),HeapDumpPath(堆转储文件的生成路径),OnOutOfMemoryError(发生OOM时的后续动作,一般情况下可以触发一个脚本自动重启服务)。

# 在触发OOM时生成堆转储文件
-XX:+HeapDumpOnOutOfMemoryError
# 堆转储文件的生成路径
-XX:HeapDumpPath=/logs/my_java_service.hprof
-- 一般会写一个脚本重启服务
# 在触发OOM后执行脚本启动服务(一般不会启动OnOutOfMemoryError)
-XX:OnOutOfMemoryError=/script/restart_my_service.sh

# 其他参数设置

# 大对象直接进入老年代

可以通过-XX:PretenureSizeThreshold参数,指定大于该设置值的对象(一般设置1M)直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。

# 长期存活的对象将进入老年代

可以通过参数-XX:MaxTenuringThreshold设置对象晋升老年代的年龄阈值。(对象通常在Eden区里诞生,如果经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。)

# 完整示例

查看完整实例
java 
# 堆内存分配
## 在指定了-Xmn的情况下-XX:SurvivorRatio这个参数是一个无效参数,可以直接去掉
## -XX:PretenureSizeThreshold=1M 超过1M的对象直接分配到老年代中
-Xms3g -Xmx3g -Xmn2048M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=1M
# 元空间内存分配
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m 
# OOM堆转储配置
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/dump.log 
# 垃圾回收器设置
## -XX:+UseParNewGC (新生代用ParNew,老年代用Serial Old)
## -XX:+UseConcMarkSweepGC (新生代用ParNew,老年代用CMS)
## 上面两个参数有矛盾,应该只有UseConcMarkSweepGC生效
## -XX:MaxTenuringThreshold 设置新生代转老年代的最大年龄,默认15,这里改成了10
## -XX:CMSInitiatingOccupancyFraction 在使用了CMS垃圾收集器的情况下,老年代内存区超过92%的使用率就会触发FullGC,这里92%的设置同样意义不是太大
## CMS使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
## -XX:CMSFullGCsBeforeCompaction 配置经过几次的FullGC进行空间碎片整理,空间碎片整理会导致STW
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=10 -XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSCompactAtFullCollection  -XX:CMSFullGCsBeforeCompaction=3
# SecureRandom在java各种组件中使用广泛,可以可靠的产生随机数。但在大量产生随机数的场景下,性能会较低。这时可以使用"-Djava.security.egd=file:/dev/./urandom"加快随机数产生过程 (ps:这个项目是大数据部门用的,确实有产生大量随机数的需求,因此设置了这个参数)
-Djava.security.egd=file:/dev/./urandom 
# 指定要启动的jar包,这里修改了实际项目的包名!
-jar /app/test-app.jar
java 
# -Xmx5120M 5g最大堆内存
-Xmx5120M 
# -Xms5120M 初始堆内存,和最大堆内存设置一致就好
-Xms5120M 
# -Xmn3072M 年轻代 3G
-Xmn3072M 
# 栈内存大小
-Xss1M 
# 当OOM时打印堆内存日志
-XX:+HeapDumpOnOutOfMemoryError
# 对应HeapDumpOnOutOfMemoryError指令的日志路径
-XX:HeapDumpPath=/logs/dump.log
# 本地内存-元空间,最小256M,最大1G,这里还有个隐藏的条件,直接内存,直接内存没设置参数,所以与-Xmn一致,是3072M
-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=1024M 
-XX:SurvivorRatio=8 
-XX:MaxTenuringThreshold=10 
-XX:PretenureSizeThreshold=3M 
-XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC 
-XX:CMSInitiatingOccupancyFraction=85 
-XX:+UseCMSCompactAtFullCollection 
-XX:CMSFullGCsBeforeCompaction=3 
-jar /umc-test-boot.jar 

# Java前期编译优化以及后期编译优化


# 前期编译优化

前期编译优化指的是将.java文件转换成.class文件的过程,这个过程一般使用javac程序来完成,这个阶段jdk做的优化其实并不多,主要是解析语法糖,将语法糖转换成对应的语法。

# 后期编译优化

后期编译优化指的是在虚拟机以解释器执行方法时,会对方法进行执行次数的统计,如果某方法执行到一定次数,就会被判定为热点方法,热点方法会编译成机器码,后续该方法不走解释器执行,而是直接通过机器码执行。与之相对的如果一个方法长期未执行,它的机器码就会被移除(转换为通过解释器执行)。

# 类加载

类加载指的是把类的.class文件加载到内存中,为后续对象创建提供支持。

# 类加载的时机?

类加载的动作是由线程执行过程中完成的,类在以下四种情况下会加载,分别是虚拟机启动过程初始化入口方法对应的类,直接使用类,反射,父类初始化。
    1)加载入口方法类
    启动java进程服务的时候,虚拟机线程会找到一个入口类,入口类对应有入口方法(main方法),虚拟机线程会对这个特殊类做类加载,然后才能被使用。
    2)直接使用类
    当我们在main线程或它的子线程中,通过new关键字实例化对象,调用类的static方法,修改/读取static属性时,会判断类是否加载,如果没有加载,类会完成它的加载过程。
    3)反射
    通过反射调用的过程中,会判断类是否完成初始化(读取常量),如果没有初始化,类会完成它的加载过程。
    4)初始化父类
    如果一个子类初始化过程中,会首先初始化它的父类,这个时候父类就会间接完成它的加载。

# 类加载的过程都做了什么?

类加载过程中会做三件事,分别是获取类的二进制流,有效性校验,初始化。

    1)获取二进制流
    加载过程第一步,我们要先读取到类的class文件的二进制流,这里有如下几种获取途径,分别是从zip包获取,从网络中获取,从动态代理中获取(自动生成)。
    2)有效性校验
    获取到二进制流之后,就要校验二进制流是否有效了,会校验文件格式,元数据,字节码,符号引用等信息。
    3)初始化
    将类的方法代码,变量名,方法名,返回值等存储到方法区。执行类里的static{}静态代码块代码,生成类的class对象,该对象会被存储到堆内存中。同时在常量池中增加类的符号引用信息,代表类初始化完成。

# 类加载器

类加载器负责加载类对象。

# 类加载器的双亲委派机制


双亲委派机制指的是如果要加载一个类,会优先使用“父类”加载器加载,如父类加载器找不到相应类,子类才会加载该类。

# 破坏双亲委派机制

双亲委派模型不是一个具有强制性约束的模型,而是Java设计者推荐给开发者们的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外的情况,双亲委派模型主要出现过3次较大规模“被破坏”的情况。
    1)jdk1.2前的代码
    2)jdk自己提供的JNDI等服务。
    3)OSGi实现模块化热部署。

# 对象创建

        当前线程在执行方法(方法对应栈区中的栈帧)的时候,如果碰到了new关键字,就会首先开始类加载以及类初始化(默认类未加载),然后会创建对象(对象会存储在堆区中的新生代),对象包含了对象头(对象的锁标志,配合监视器锁就构成了所谓的管程,这也是synchronized同步关键字的原理。此外对象头里还存了对象的分代年龄,这也是垃圾回收的一个重要知识点)以及实例数据。
        此时如果做了线程间的切换,就会把当前执行的字节码的行号记录到程序计数器中(这里假定线程继续执行)。如果这个时候执行了新创建对象的方法,就会把新对象的方法压入到当前线程的栈区。此时,如果执行的方法操作了新对象的实例属性,就会在当前线程的工作内存中创建该属性的副本。

# 对象创建流程


# 对象的布局

对象由对象头,实例数据,对齐填充三部分组成,也就是每一个对象占用内存大小都一致。其中对象头大小也是固定长度的,对象头为32个byte大小,对象头由标记数据(也称为MarkWrod)+其他数据组成。标记数据的标记不同,其他空间存储的数据也不同(可以说是极限复用内存空间了)。

# 对象头中MarkWord与其他数据的对应表

MarkWord标记 MarkWord业务含义 其他数据
001 无锁状态 对象哈希值、分代年龄
101 偏向锁状态 偏向线程ID、偏向时间戳、对象分代年龄
00 轻量级锁状态 指向锁记录的指针
10 重量级锁状态 指向重量级锁的指针
11 GC标记状态 空,不需要记录信息

# Java对象的访问定位


# Java内存模型

# 主内存与工作内存


Java内存模型的主要目的是定义程序中各种变量的访问规则,既关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。此处的变量与Java中的变量的定义有区别,它包括了实例字段、静态资源和构成数组对象的元素,但是不包括局部变量与方法参数(这两者是线程私有的,不会被共享,自然就不存在竞争问题)。
Java内存模型规定所有的变量都存储在主内存中。每条线程都有自己的工作内存,线程的工作内存中保存了该线程使用的变量的主内存副本。线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,线程不能直接读写主内存中的数据。不同线程之间也无法直接访问对方工作内存中的数据。线程间变量值的传递需要通过主内存来完成。

# 内存间交互操作

关于主内存和工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如果从工作内存同步回主内存的具体实现细节,Java内存模型定义了以下8种操作来完成。Java虚拟机实现时必须保证单一操作的原子性。
    1)lock(锁定)
    作用于主内存的变量,它把一个变量标识为线程独占的状态。
    2)unlock(解锁)
    作用于主内存的变量,它把一个变量从线程独占状态释放。
    3)read(读取)
    作用于主内存的变量,将变量从主内存拷贝到工作内存,便于load操作执行。
    4)load(载入)
    作用于工作内存的变量,把read操作的值放到工作内存的变量副本中。
    5)use(使用)
    使用工作内存中的变量。它把工作内存中的变量的值传递给执行引擎。每当虚拟机遇到一个需要变量的值的字节码指令时就会执行这个操作。
    6)assign(赋值)
    作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
    7)store(存储)
    作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
    8)write(写入)
    作用于主内存的变量,他把store操作从工作内存中得到的变量的值放入主内存。

# volatile关键字的含义?

volatile是Java提供的最轻量级的同步机制,当一个变量被定义成volatile后,它将具备两个特性:

  1. 可见性:当一个线程修改了变量的值,新值对于其他线程来说是立即可见的(看起来就像是直接修改了主内存的值一样,实际还是修改的工作内存的值)。
  2. 禁止指令重排序优化:在单线程环境下,JVM在不改变程序的最终执行结果的前提下,从优化的角度会对指令重新排序执行。指令重排序在单线程下完美无缺,但在多线程环境下,它就可能引发严重的问题。因为一个线程的代码执行顺序,可能会被另一个线程“观测”到,从而导致意想不到的结果。使用volatile修饰变量后,JVM会使用内存屏障(Memory Barrier)来禁止指令重排序。

volatile变量不是线程安全的

volatile只提供了可见性和禁止指令重排序两个特性,针对复合操作,volatile并不保证线程安全(如i++操作就是一个复合操作)。

package com.kieoo.interview;

/**
 * volatile是java提供的最轻量级的同步机制
 * Java同步器就依赖volatile以及CAS实现
 * 同步器又是AQS的基石,AQS又是ReentrantLock的基石
 * 因此必须了解掌握
 */
public class VolatileTest {
    public static volatile int race = 0;

    private static final int THREADS_COUNT = 20;

    public static void increase() {
        race++;
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int i1 = 0; i1 < 10000; i1++) {
                    increase();
                }
            });
            // 启动线程
            threads[i].start();
        }
        /**
         * 主线程+20个子线程,一共21个,这里这句话的意思是只要还有子线程存在就休眠主线程
         * Thread.activeCount():返回线程组里的线程数,这个线程组里主线程一个,子线程20个
         * Thread.yield():当前线程休眠,也就是主线程休眠
         */
        while (Thread.activeCount() > 1)
            Thread.yield();
        // 打印race
        System.out.println(race);
    }
}


如何使用volatile

由于volatile只保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁来保证原子性:

  1. 运算结果并不依赖变量的当前值,或者能够保证只有单一线程能修改变量的值。
  2. 变量不需要与其他的状态变量共同参与不变约束。
	volatile boolean shutdownRequested;

	public void shutdown(){
		shutdownRequested = true;
	}

	public void doWork(){
		while(!shutdownRequested){
			// 代码执行逻辑
		}
	}

此外,volatile+CAS配合使用,组成Java同步器是它最经典的使用场景。

使用volatile创建DCL(Double Check Lock)单例
public class Singleton {
    // 使用 volatile 关键字禁止指令重排序
	// 不适用volatile修饰的话,不会产生多个实例,但会产生更严重的问题:返回未完全初始化的对象。
	// 因为对象创建不是原子操作,而是如下步骤:
	// 分配内存空间(为 Singleton 对象分配内存)
	// 初始化对象(调用构造函数,设置字段值)
	// 将引用指向内存地址(让 instance 指向这块内存)
	// 因此需要禁止指令重排序
    private static volatile Singleton instance;
    
    // 私有构造方法,防止外部实例化
    private Singleton() {
        // 防止通过反射创建实例
        if (instance != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
        }
    }
    
    /**
     * 双重检查锁定实现单例模式
     */
    public static Singleton getInstance() {
        // 第一次检查:如果实例已经存在,直接返回,避免不必要的同步
        if (instance == null) {
            // 同步代码块,确保线程安全
            synchronized (Singleton.class) {
                // 第二次检查:防止多个线程同时通过第一次检查后创建多个实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    // 示例方法
    public void showMessage() {
        System.out.println("Hello from Singleton instance! HashCode: " + this.hashCode());
    }
}

# Java内存模型中原子性、可见性、有序性分别指什么?

# 原子性

原子性指的是某一个操作不可分割,在Java多线程操作对象时,Java内存模型提供的read、write、lock、unlock等指令都是原子操作。在代码层面,针对基本类型变量的读写对应内存模型中的read以及write指令,是原子性的。如果我们需要针对保证复合指令(如i++)的原子性,可以使用synchronized关键字来实现,synchronized的底层对应monitorenter以及monitorexit指令,最终底层指令对应lock以及unlock指令。

# 可见性

可见性是指当一个线程修改了共享变量的值,其他线程能否立刻感知到。根据Java内存模型可以知道,针对普通变量的修改其他线程不能立刻感知到,针对经volatile修饰的变量的修改是可以立刻感知到的。
此外根据这个定义,synchronized和final一样满足可见性。只不过synchronized是通过阻塞其他线程查看该对象类实现的。而final是通过不可变来实现的。

# 有序性

编译器有时候为了优化性能,会改变指令的执行顺序,在并发编程时,需要确保指令的有序性,以避免多线程执行时的bug。
Java程序中天然的有序性可以总结为一句话:
如果在当前线程中观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的(指令重排序、工作内存与主内存同步延迟)。
同样的Java提供了volatile以及synchronized两个特性来保证多线程之间的有序性。

# 先行发生原则

如果Java内存模型中所有的有序性都依靠volatile以及synchronized来保证,那么有很多操作都会变得非常啰嗦,但是实际我们开发Java并发编程时并没有察觉到这一点,这是因为Java语言中有一个“先行发生”原则。这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条规则一揽子解决并发环境下两个操作是否存在冲突的所有问题,而不必陷入Java内存模型苦涩难懂的定义中。
下面是Java内存模型下一些“天然的”先行发生关系,这些先行发生关系无需任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,则他们就没有顺序性保证,虚拟机可以随意地进行重排序。

  • 程序次序规则(Program Order Rule)
    在一个线程中,按照控制流执行,书写在前面的操作先行发生于书写在后面的操作。(如两个if代码块,上面的代码块的代码一定先于下面的代码块的代码执行)

  • 管程锁定规则(Monitor Lock Rule)
    一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。

  • volatile变量规则(Volatile Variable Rule)
    对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里“后面”同样指的是时间上的先后。

  • 线程启动规则(Thread Start Rule)
    Thread对象的start()方法先行发生于此线程的每一个动作。

  • 线程终止规则(Thread Termination Rule)
    一个线程中的所有操作都先行发生于其他线程检测到该线程已经终止之后的操作。

  • 线程中断规则(Thread Interruption Rule)
    对一个线程的 interrupt() 方法的调用先行发生于被中断线程检测到中断事件的发生。如果线程A调用了threadB.interrupt(),那么线程A在调用interrupt() 之前的所有操作对线程B在检测到中断之后的所有操作都是可见的。

  • 对象终结规则(Finalizer Ruler)
    一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

  • 传递性(Transitivity)
    如果操作A先行发生于操作B,操作B先行发生于操作C,那么可以得出操作A先行发生于操作C的结论。

# Java线程

# 线程的实现

# 内核线程实现

使用内核线程实现的方式称为1:1实现。内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多个事情,支持多线程的内核就称为多线程内核(Multi-Threads Kernel)。
程序一般不会使用内核线程,而是使用内核线程的一种高级接口(轻量级进程Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型。JDK8中使用的线程模型就是内核线程实现。

# 用户线程实现

使用用户线程实现的方式被称为1:N实现。广义上来讲,一个线程只要不是内核线程,都可以认为是用户线程(User Thread,UT)的一种,因此从这个定义上看,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,因此效率会受到限制,并不具备通常意义上的用户线程的优点。
而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及如何实现的。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也能够支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。
用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要由用户程序自己去处理。线程的创建、销毁、切换和调度都是用户必须考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至有些是不可能实现的。因为使用用户线程实现的程序通常都比较复杂,除了有明确的需求外(譬如以前在不支持多线程的操作系统中的多线程程序、需要支持大规模线程数量的应用),一般的应用程序都不倾向使用用户线程。Java、Ruby等语言都曾经使用过用户线程,最终又都放弃了使用它。但是近年来许多新的、以高并发为卖点的编程语言又普遍支持了用户线程,譬如Golang、Erlang等,使得用户线程的使用率有所回升。

# 混合实现

线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。在这种混合实现下,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,这大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量比是不定的,是N:M的关系,这种就是多对多的线程模型。JDK21中虚拟线程技术使用的就是混合实现模型。

# 线程调度策略

线程调度是指系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式(Cooperative Threads-Scheduling)线程调度和抢占式(Preemptive Threads-Scheduling)线程调度。Java使用的线程调度方式就是抢占式调度
如果使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。譬如在Java中,有Thread::yield()方法可以主动让出执行时间,但是如果想要主动获取执行时间,线程本身是没有什么办法的。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会有一个线程导致整个进程甚至整个系统阻塞的问题。

# 线程状态

在Java中,线程的状态被定义在java.lang.Thread.State枚举中,共有6种状态:

  1. NEW(新建)
    当线程对象被创建但还没有调用start()方法时,线程处于NEW状态。
  2. RUNNABLE(可运行)
    当线程调用了start()方法后,线程处于RUNNABLE状态。注意,RUNNABLE状态包括正在运行和准备运行(就绪)两种情况,即线程可能正在执行,也可能在等待CPU时间片。
  3. BLOCKED(阻塞)
    线程在进入同步方法/同步块时,如果该同步方法/同步块已经被其他线程占用,则线程会进入BLOCKED状态,直到获取到锁。
  4. WAITING(等待)
    当线程调用了Object.wait()、Thread.join()或LockSupport.park()方法时,线程会进入WAITING状态。进入该状态的线程需要等待其他线程通知(notify或notifyAll)或中断(interrupt)才能回到RUNNABLE状态。
  5. TIMED_WAITING(超时等待)
    与WAITING状态类似,但带有超时时间。当线程调用了Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()或LockSupport.parkUntil()方法时,线程会进入TIMED_WAITING状态。当超时时间到达或收到通知/中断时,线程会回到RUNNABLE状态。
  6. TERMINATED(终止)
    当线程的run()方法执行完毕或者因为异常而退出时,线程进入TERMINATED状态。


# 线程安全

# 线程安全的定义

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要做额外的同步,或者在调度方进行任何其他协调操作,调用这个对象的行为都可以获取到正确的结果(读写对象的属性),那么就称这个对象是线程安全的。

# Java中的线程安全

Java中,主要有以下几类线程安全的对象,分别是不可变对象,线程对立对象,绝对线程安全对象,先对线程安全对象,线程兼容对象。

    1) 不可变对象 常见
    不可变对象一定是线程安全的,对于基本类型变量我们只需要声明为final即可,对于引用类型变量,除了变量本身要声明为final,变量中的其他变量同样要声明为final。
    2) 绝对线程安全对象
    绝对线程安全是不管运行是环境如何,调用者都不需要任何额外的同步措施(线程只需要用对象就行,不需要做额外操作)。通常需要付出很大的甚至不切实际的代价才能创建出这样的对象。在java中,我们可以认为这种对象不存在
    3) 相对线程安全对象 常见
    就是通常意义的线程安全,确保这个对象单独的操作是线程安全的(单个操作是线程安全的,复合操作是非线程安全的,针对复合操作,需要额外的同步策略来保证线程安全)。Java语言中,大部分声称线程安全的类都属于这种类型,如:Vector、Hashtable等(类的所有属性,方法都使用了synchronized或者锁等机制保证了线程安全)。

Vector是相对线程安全的类
/**
 * 实际执行会报如下的错误:
 * Exception in thread "Thread-309" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 8
	at java.util.Vector.get(Vector.java:753)
	at com.kieoo.interview.VectorNoSyncTest.lambda$main$1(VectorNoSyncTest.java:20)
	at java.lang.Thread.run(Thread.java:750)
Exception in thread "Thread-379" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 5
	at java.util.Vector.get(Vector.java:753)
	at com.kieoo.interview.VectorNoSyncTest.lambda$main$1(VectorNoSyncTest.java:20)
	at java.lang.Thread.run(Thread.java:750)

	报错原因:虽然Vector是线程安全类,但是针对下面代码的执行,实际上是复合操作
	尽管Vector的每个方法都是线程安全的,但是整个复合操作(例如先检查size,然后根据size来执行多个操作)并不是线程安全的。这就是相对线程安全(也称为线程安全)和绝对线程安全的区别。
	for (int i = 0; i < vector.size(); i++) {
        vector.remove(i);
    }
 */
package com.kieoo.interview;

import java.util.Vector;

public class VectorNoSyncTest {

    private static Vector<Integer> vector = new Vector<>();
    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }
            Thread removeThread = new Thread(() -> {
                for (int i = 0; i < vector.size(); i++) {
                    vector.remove(i);
                }
            });
            Thread printThread = new Thread(() -> {
                for (int i = 0; i < vector.size(); i++) {
                    System.out.println((vector.get(i)));
                }
            });
            removeThread.start();
            printThread.start();
            //不要同时产生过多的线程,否则会导致操作系统假死
            while (Thread.activeCount() > 20) ;
        }
    }
}
如何确保Vector的线程安全
  1. 如果要确保Vector的线程安全,需要在调用方做额外的同步:
package com.kieoo.interview;

import java.util.Vector;

public class VectorSyncTest {

    private static Vector<Integer> vector = new Vector<>();
    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }
            Thread removeThread = new Thread(() -> {
				// 在调用方,也就是线程中针对vector做额外的同步(加锁)来保证线程安全
                synchronized (vector){
                    for (int i = 0; i < vector.size(); i++) {
                        vector.remove(i);
                    }
                }
            });
            Thread printThread = new Thread(() -> {
                synchronized (vector){
                    for (int i = 0; i < vector.size(); i++) {
                        System.out.println((vector.get(i)));
                    }
                }
            });
            removeThread.start();
            printThread.start();
            //不要同时产生过多的线程,否则会导致操作系统假死
            while (Thread.activeCount() > 20) ;
        }
    }
}

    4) 线程兼容对象 常见
    线程兼容指的是对象本身不是线程安全的(如一个对象的属性未经synchronized修饰,这种对象在java中占比最多),但是可以通过调用方正确的使用同步来保证它是线程安全的。
    5) 线程对立对象
    线程对立指的是不管调用方是否使用了同步策略,都无法在多线程环境下并发使用。一般不需要考虑线程对立的情况。

# 线程安全的实现方式?

根据上面Java中的线程安全的定义,不考虑不可变对象的话,Java中只有相对线程安全对象以及线程兼容对象,针对这两种对象的复合操作,调用线程都需要额外的方式确保线程安全,有三种方式可以实现线程安全,分别是互斥同步,非阻塞同步,无同步方案。

# 互斥同步

synchronized和ReentrantLock都可以实现互斥同步。同时reentrantLock多了一些高级特性,如等待可中断,公平锁,锁可以同时绑定多个条件。

# 非阻塞同步

通过CAS(Compare And Swap比较并交换,操作系统提供的一个原子操作)来实现非阻塞同步,相较互斥同步性能要好一些。

改进后的AtomicTest
  1. 改进前的自增操作:
package com.kieoo.interview;

/**
 * volatile是java提供的最轻量级的同步机制
 * Java同步器就依赖volatile以及CAS实现
 * 同步器又是AQS的基石,AQS又是ReentrantLock的基石
 * 因此必须了解掌握
 */
public class VolatileTest {
    public static volatile int race = 0;

    private static final int THREADS_COUNT = 20;

    public static void increase() {
        race++;
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int i1 = 0; i1 < 10000; i1++) {
                    increase();
                }
            });
            // 启动线程
            threads[i].start();
        }
        /**
         * 主线程+20个子线程,一共21个,这里这句话的意思是只要还有子线程存在就休眠主线程
         * Thread.activeCount():返回线程组里的线程数,这个线程组里主线程一个,子线程20个
         * Thread.yield():当前线程休眠,也就是主线程休眠
         */
        while (Thread.activeCount() > 1)
            Thread.yield();
        // 打印race
        System.out.println(race);
    }
}
  1. 改进后的自增操作:
// 这个类的运行结果是200000
package com.kieoo.interview;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicTest {
    // AQS中state用的是volatile int state;为什么这里用的是AtomicInteger呢?
    // 首先是AtomicInteger内部也用了private volatile int value;类修饰真正的int对象
    // 其次是AQS中不想使用AtomicInteger的封装
    public static AtomicInteger race = new AtomicInteger(0);

    private static final int THREADS_COUNT = 20;

    public static void increase() {
        race.incrementAndGet();
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int i1 = 0; i1 < 10000; i1++) {
                    increase();
                }
            });
            // 启动线程
            threads[i].start();
        }
        /**
         * 主线程+20个子线程,一共21个,这里这句话的意思是只要还有子线程存在就休眠主线程
         * Thread.activeCount():返回线程组里的线程数,这个线程组里主线程一个,子线程20个
         * Thread.yield():当前线程休眠,也就是主线程休眠
         */
        while (Thread.activeCount() > 1)
            Thread.yield();
        // 打印race
        System.out.println(race);
    }
}

# 无同步方案

可重入代码以及线程本地存储ThreadLocal(用的较多)可以实现无同步方案。

什么是可重入代码?

可重入代码(Reentrant Code) 是指一段可以被多个线程同时调用而不会出现数据不一致或状态错误的代码。更具体地说,可重入代码在执行过程中不依赖于任何共享状态,或者对共享状态的访问是线程安全的。以下是可重入代码的一些示例:

  1. Servlet 开发
public class StatelessServlet extends HttpServlet {
    // 可重入:不依赖实例变量
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) {
        String name = request.getParameter("name");
        String greeting = "Hello, " + (name != null ? name : "World");
        response.getWriter().write(greeting);
    }
}
  1. 函数式编程
public class FunctionalExamples {
    // 可重入:函数式风格
    public static List<Integer> processNumbers(List<Integer> numbers) {
        return numbers.stream()
                     .filter(n -> n % 2 == 0)
                     .map(n -> n * n)
                     .collect(Collectors.toList());
    }
}
  1. 工具类设计
// 工具类通常设计为可重入的
public final class ValidationUtils {
    private ValidationUtils() {}  // 防止实例化
    
    public static boolean isValidEmail(String email) {
        if (email == null) return false;
        return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
    }
    
    public static boolean isPositiveNumber(int number) {
        return number > 0;
    }
}

# JVM针对synchronized同步锁做了哪些优化?

# 锁消除

锁消除的判断依据基于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把他们当做栈上数据对待,认为它们是线程私有的,同步加锁自然也无需进行了。这个时候就可以直接把锁给“消除”掉了。

# 锁粗化

如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩大(粗化)到整个操作序列的外部,这个过程就叫锁粗化。

# 自旋锁

如果两个线程同时请求锁对象,那么第一个线程就可以获取到锁对象,第二个线程会被挂起(后续线程调度策略会重新将这个挂起的线程恢复),对于Java虚拟机来说,线程的挂起与恢复本身就是重量级操作,对性能有影响,因此,我们考虑不挂起线程,而是让没有获取锁对象的线程执行一个忙等待(自旋),自旋结束后再尝试获取锁对象,这个过程就称为自旋锁(个人理解是不是叫“线程自旋”会好一些)。当然线程不会一直自旋,如果自旋超过10次还没有获得锁,线程就会阻塞。

# 轻量级锁

传统的互斥锁是基于操作系统提供的互斥量特性来实现的,所以称为重量级锁。在无竞争的情况下,轻量级锁使用CAS操作修改对象头中的标志位(如果两个以上线程修改标志位,标志位就会变成2,此时轻量级锁会膨胀为重量级锁)来实现锁的获取和释放,避免了线程的阻塞和唤醒,从而提高了并发性能。

# 轻量级锁的加锁过程:

  1. 当线程进入同步块时,如果同步对象没有被锁定(锁标志位为01),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝(官方称为 Displaced Mark Word)。

  2. 然后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位将转变为00,表示此对象处于轻量级锁定状态。

  3. 如果这个更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则,说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为10,Mark Word 中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。(第一个获取锁的对象把锁改成了它自己的堆栈的拷贝,第二个尝试获取锁的对象把锁的标记改成了重量级锁以及把Mark Work改成了重量级锁的指针)。

# 轻量级锁的解锁过程:

  1. 当前获取锁的对象通过 CAS 操作把线程栈帧中的 Displaced Mark Word 替换回对象的 Mark Word。如果替换成功,整个同步过程就完成了(这里其实就是比较是不是第一次加锁,因为第二次加锁的话,锁会改成重量级锁的指针,以及锁标记也会改变,所以比较并交换就会失败)。

  2. 如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程(此时已经膨胀为重量级锁)。

# 偏向锁

偏向锁的目的是消除数据在无竞争条件下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争条件下使用CAS操作替代线程的挂起与恢复,那偏向锁就是在无竞争的情况下把整个同步操作都消除掉,连CAS操作都不用去做了。(与轻量级锁一样,偏向锁也是通过修改对象头的标志位来实现的,同样的,如果有2个以上线程同时操作1个对象,偏向锁会立刻失效)。
当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如加锁、解锁及对Mark Word的更新操作等)。
一旦出现另外一个线程去尝试获取这个锁的情况,偏向模式就马上宣告结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向(偏向模式设置为“0”),撤销后标志位恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就按照上面介绍的轻量级锁那样去执行。

# 垃圾回收

# 如何判断对象是否存活

有两类算法可以判断对象是否存活,分别是引用计数算法以及可达性分析算法。

# 引用计数算法

在jvm中,没有一款虚拟机的垃圾回收使用引用计数算法,因为该算法会产生循环引用问题,且该问题无法处理。

# 可达性分析算法

可达性分析算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

java中哪些对象可以作为GC Root?

  1. 在虚拟机栈中局部变量表中reference类型的引用对象
  2. 元空间(方法区)中静态属性引用的对象,对应代码里面就是一个类中经static修饰的属性
  3. 被同步锁synchronized锁定的对象
  4. 虚拟机内部实现的一些对象
  5. 本地方法栈中的属性,与虚拟机栈局部变量表中reference类型的引用对象是对应关系

# Java中强软弱虚引用分别指什么?如何实现?有什么使用场景

引用类型 说明 如何实现 使用场景
强引用 只要强引用存在,垃圾回收器就永远不会回收掉被引用的对象。 继承Object的类都是强引用类型 几乎所有场景。
软引用 软引用会在OOM之前回收(内存不足时回收)。 使用SoftReference类 内存敏感的高速缓存:例如缓存图片、数据等。当内存充足时,缓存存在以提升速度;当内存不足时,缓存会被自动清除,避免OOM。
弱引用 弱引用会在GC之后回收。 使用WeakReference类 在某些框架中,用于存储监听器,当监听器对象本身不再被其他地方强引用时,可以自动从列表中移除。
虚引用 虚引用随时可能被回收。 使用 PhantomReference 类,并且必须与引用队列 ReferenceQueue 联合使用。它的唯一作用就是:跟踪对象被垃圾回收的事件。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收该对象后,将这个虚引用加入到与之关联的引用队列中。 在对象被回收时收到一个系统通知。一个典型应用是管理堆外内存(如NIO的DirectBuffer)。DirectByteBuffer 对象本身是一个Java对象,但它背后关联了一块操作系统管理的堆外内存。通过虚引用,可以在 DirectByteBuffer 对象被回收时,接到通知,从而释放其背后的堆外内存,防止内存泄漏。

# 分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(Generational Collection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
    1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
    2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
根据分代收集理论,我们将对象分别存放在新生代以及老年代中。

# 垃圾回收算法

下面提到的垃圾回收算法的实现都基于可达性分析算法以及分代收集理论实现。此外,标记-复制算法以及标记-整理算法也是基于标记-清理算法实现。

# 标记-清除算法

    最基础的垃圾收集算法是“标记-清除”(Mark-Sweep)算法。如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程(通过可达性分析算法判定)。
    下面提到的标记-复制以及标记-整理算法都是基于标记-清扫算法进行改进。标记之后啥也不做就直接清理掉,就是标记清除算法。显然,这个算法有很大的改进空间,而且执行效率低。在新生代中,我们可以用标记-复制算法进行改进。而在老年代中,我们可以用标记-整理算法进行改进。

# 标记-复制算法 新生代使用

    标记-复制算法常被简称为复制算法。为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,1969年Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这个算法也有个问题,那就是存在内存翻倍的情况。

新生代如何实现标记-复制算法

实际虚拟机实现标记-复制算法时,会将内存区域分为三块,一块是使用中内存Eden,占内存的80%,另外两块分别占10%,称为Surviver From区以及Surviver To区,这样只需要浪费10%的内存就能实现标记-复制算法了。
这个方案还有个问题,那就是垃圾回收之后剩余的对象可能超过10%,因此还有一个备用的逃生区的方案可供使用(如果超过10%,会临时占用老年代/其他地方的内存)。

# 标记-整理算法 老年代使用

    标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
    针对老年代对象的存亡特征,1974年Edward Lueders提出了另外一种有针对性的“标记-整理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

# Hotspot中如何快速获取所有的gcroot?

  1. 针对非线程栈中的根节点,直接获取即可,如方法区中的根节点就是直接获取的。
  2. 针对线程栈中的根节点,在GC暂停时,线程需要走到当前栈帧的“安全点”,然后获取安全点的OopMap,即可获取gcroot对象,而安全点对应的OopMap则是在编译节点就可以确定下来的(这可以极大的提升性能)。针对非当前栈帧的OopMap,栈帧切换本身就是一个安全点,因此其他栈帧本身就已经在安全点了,只需要获取OopMap即可。

# 安全点的概念

在GC STW时,当前线程也不能说停就停的,而是要执行到一些特定的代码(不会导致线程问题)的指令的时候,才会停下来,这些特定指令就称之为安全点,JVM在编译阶段就会生成安全点的OopMap,用于快速获取当前线程中的GC ROOT。

# 安全区的概念

针对正在执行的线程,我们当前可以要求栈帧走到特定的安全点,这里有个特殊场景,那就是线程休眠,休眠线程当然也有线程栈,在GC的时候,我们无法操作休眠线程,因此定义了安全区的概念(简单理解成线程GC Root遍历的一个特殊场景即可)。

安全点和安全区的区别?
特性 安全点 (Safepoint) 安全区 (Safe Region)
本质 代码中的特定位置 代码的一段区域
范围 点状分布 连续区域
线程状态 线程正在执行 线程可能处于阻塞状态
进入方式 执行到特定位置 在特定代码区域中
  1. 当GC发生时,还在活跃的应用线程会收到通知,此时线程会主动往安全点执行,执行到安全点之后线程就会挂起。在线程还没走到安全点的时候,很可能它会走到安全区,这时候它会被动的挂起(被GC线程挂起)。
  2. 当GC时,线程本身已经处于Sleep或者Blockd状态了,就说明它本身就处在安全区(引用关系不会发生变化)。针对这部分线程就不需要再做其他动作了,直接挂起就行。

# 如何划分安全区?

安全区不像安全点,安全点是有明确的定义的,安全区则是由jvm动态划分的,只要对象的引用关系不再发生变化,就会被jvm动态划分为安全区。
在 Java 中,安全区(Safe Region)的划分是由 JVM 自动管理的,而不是由开发者显式划分。JVM 通过字节码分析和运行时监控来确定哪些代码区域是安全区。

# Hotspot如何处理跨代引用

这里特指老年代中引用新生代的跨代引用场景,且新生代触发MinorGC的场景(新生代引用老年代不是问题,无需考虑)。以下是一些可能的方案:

  • 遍历老年代
    理论上当然可以解决问题,实际上性能就奇差无比了,因为每次回收新生代,都要把老年代作为GCRoot去遍历。

  • 在新生代存储Map对象
    在新生代创建一个Map对象,当老年代引用新生代时,就把老年代作为key,新生代的集合作为value存储,这个方式当然也能实现,但是每次老年代引用修改都要修改这个Map对象,开销依然很大。

  • 记忆集与卡表 记忆集是一个理论概念,这里不展开,而卡表是积极集的具体实现,在只触发MinorGC的情况下,我们可以将老年代分为一个一个的“块”,针对每一个块使用一个标记,只要这个块中有任意一个老年代对象引用了新生代对象,这个块的标记就会是1(修改这个标记的时候会使用斜面提到的“写屏障”),这样在触发MinorGC的时候,我们就能快速找到老年代中的块,把这些块的老年代对象作为GCRoot,就可以找到那些被老年代引用的新生代对象了(解决跨代引用问题)。

# 什么是写屏障?

我们已经解决了如何使用记忆集来缩减GC Roots扫描范围的问题,但还没有解决卡表元素如何维护的问题,例如它们何时变脏、谁来把它们变脏等。
卡表元素何时变脏的答案是很明确的——有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻。但问题是如何变脏,即如何在对象赋值的那一刻去更新维护卡表呢?假如是解释执行的字节码,那相对好处理,虚拟机负责每条字节码指令的执行,有充分的介入空间;但在编译执行的场景中呢?经过即时编译后的代码已经是纯粹的机器指令流了,这就必须找到一个在机器码层面的手段,把维护卡表的动作放到每一个赋值操作之中。
在HotSpot虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的。写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。

# Hotspot中如何在线程并发执行时分析对象是否可达?

如果在STW开始后,所有业务线程都暂停,GC线程才开始做可达性分析的话,显然性能也会非常差,因此我们需要在业务线程执行的情况下,使用多个GC线程做可达性分析(通过三色标记来识别)。

  1. 在获取GC Root的STW发生之后,将所有对象都标记为白色
  2. 允许多个GC线程和多个用户线程并行执行,这一阶段用户线程新增的对象显然不属于白色,这部分新增对象和GC无关。
  3. GC线程遍历GCRoot,被遍历的引用会首先标记为灰色,然后GC线程会遍历引用对象的引用对象,当引用对象的引用对象都被遍历过之后,当前引用对象会被标记为黑色(不会被回收)。如果其他GCRoot碰到黑色对象,直接跳过即可。
  4. 随着GC线程遍历GCRoot的执行,会产生很多灰色的对象,针对灰色对象又会做判断,最终处理结果是灰色对象不存在,此时对象一定是非黑即白的。黑色对象代表存活对象,白色对象代表回收对象。

# 三色标记中业务线程可能会修改引用,最终导致对象误删除,如何处理?

在GC线程做标记的过程中,业务线程当然是可以随意修改对象之间的引用关系的,因此理论上来说,就有可能导致两种情况,一个是不该回收的被标记成回收,另外一个是该回收的没回收(这个不管,下次回收即可),针对“对象消失”的问题,需要重点处理。
Wilson于1994年在理论上证明了,当且仅当以下两个条件同时满足时,会产生“对象消失”的问题,即原本应该是黑色的对象被误标为白色:

  • 赋值器插入了一条或多条从黑色对象到白色对象的新引用。
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
    因此,我们要解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可。由此分别产生了两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)。

# 原始快照解决对象消失问题

原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来(通过写屏障来实现,这里写屏障可以简单理解成虚拟机层面的AOP操作,修改引用关系就会触发写屏障),在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

# 垃圾回收器的实现思路

垃圾回收器的实现思路无非是分代收集,在收集时会触发STW(Stop The World),剩下的无非是针对新生代及老年代的收集,是使用单线程收集还是多线程收集的问题,新生代收集只有唯一方案(标记-复制),老年代收集也只有唯一方案(标记-整理)。所以可能的组合是1-1,1-N,N-1,N-N这四种。

# 垃圾回收器-新生代

# Serial收集器

Serial是最早也是最简单的新生代垃圾回收器(使用标记-复制算法实现)。Serial垃圾回收器在工作的时候会触发STW(Stop The World),唯一的一个GC线程会在Safepoint安全点暂停所有用户线程,然后进行gc垃圾回收。

# ParNew收集器

ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。

# Parallel Scavenge收集器 jdk8默认新生代收集器

    Parallel Scavenge同样是基于标记-复制算法实现的多线程并发的新生代收集器(也叫做自适应吞吐量优先收集器)。与其他收集器不同(CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间),Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(吞吐量=(运行用户代码时间)/(运行用户代码时间+运行垃圾收集时间))。
    如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。
使用Parallel Scavenge时,会触发STW,此时用户线程完全暂停,只有GC线程在并发的做GC工作。

自适应吞吐量优先收集器

    jdk8中默认使用Parallel Scavenge收集器,且默认使用-XX:+UseAdaptiveSizePolicy做垃圾收集的自适应的调节策略(虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数以提供最合适的停顿时间或者最大的吞吐量。),因此,在jdk8中,我们只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis参数(更关注最大停顿时间)或-XX:GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标即可。
    -XX:MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。
    -XX:GCTimeRatio参数的值是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%(即1/(1+19)),默认值为99,即允许最大1%(即1/(1+99))的垃圾收集时间。

# 垃圾回收器-老年代

# Serial Old收集器

    Serial Old是最早也是最简单的老年代垃圾回收器(使用标记-整理算法实现)。Serial Old垃圾回收器在工作的时候会触发STW(Stop The World),唯一的一个GC线程会在Safepoint安全点暂停所有用户线程,然后进行gc垃圾回收。

# Parallel Old收集器 jdk8默认老年代收集器

    Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合(jdk8中Parallel Scavenge以及Parallel Old是默认的新生代以及老年代收集器)。
使用Parallel Old时,会触发STW,此时用户线程完全暂停,只有GC线程在并发的做GC工作。

# CMS收集器

    CMS(Concurrent Mark Sweep并发标记清除)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于标记-清除算法实现的,它的运作过程分为四个步骤,包括:
    1)初始标记(CMS initial mark)
    2)并发标记(CMS concurrent mark)
    3)重新标记(CMS remark)
    4)并发清除(CMS concurrent sweep)
    其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GCRoots能直接关联到的对象,速度很快;并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(详见3.4.6节中关于增量更新的讲解),这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的

    CMS是一款优秀的收集器,它最主要的优点在名字上已经体现出来:并发收集、低停顿,一些官方公开文档里面也称之为“并发低停顿收集器”(Concurrent Low Pause Collector)。CMS收集器是HotSpot虚拟机追求低停顿的第一次成功尝试,但是它还远达不到完美的程度,而且缺点很多,CMS只是老年代的垃圾回收器,它还需要和一个新生代垃圾回收器配合使用,然而CMS并不支持与Parallel Scavenge一起使用。因此,只能搭配使用ParNew收集器,且同时CMS还会触发了Concurrent Mode Failure,这时还需要使用Serial Old收集器。基于以上原因,不推荐使用CMS收集器作为老年代收集器。

# G1垃圾回收器 jdk9及以上默认垃圾收集器

    Garbage First(简称G1)是“停顿时间模型”收集器。它的核心设计目标之一就是支持在一个长度为M毫秒的时间段内,消耗在GC的时间大概率不超过N毫秒的目标。它不再使用固定的区域作为新生代,老年代,而是将堆区划分为一个一个小的分区Region。每一个Region可以扮演新生代的Eden空间、Survivor空间,或者老年代空间。针对不同的分区,G1有不同的回收策略。
    G1收集器可以跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。
    G1收集器的运作过程大致可划分为以下四个步骤:
    1)初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
    2)并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SATB记录下的在并发时有引用变动的对象。
    3)最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。
    4)筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

    从上述阶段的描述可以看出,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的, 换言之,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐 量,所以才能担当起“全功能收集器”的重任与期望。

G1的停顿时间模型参数如何设置

在G1中,我们可以通过-XX:GCPauseIntervalMillis参数设置两次GC开始的时间间隔,通过-XX:MaxGCPauseMillis设置GC的期望停顿时间。
如我们设置GCPauseIntervalMillis参数为1000,MaxGCPauseMillis参数为100,也就是1秒钟最多只允许一次GC,GC时间不超过100ms。

# 低延迟收集器(Shenandoah)

# 传统收集器中用户线程如何获取GC后的对象地址

我们都知道,GC发生时,GC线程会修改存活对象的内存地址,此时用户线程是暂停的,那么用户线程恢复时,如何找到已经被修改过地址的对象呢? 这个问题的关键在于安全点,在安全点及安全区,GC线程可以获取所有的对象引用信息,那么GC线程恢复用户线程执行之前,理所当然的会修改用户线程的局部变量表中的引用关系。这样用户线程恢复之后就能重新获取到对象的引用了。

# 低延迟收集器的并发收集特性

低延迟收集器(如ZGC以及Shenandoah,他们相较G1的提速关键就在于允许用户线程和GC线程在垃圾清理阶段并发执行)。这里就会引出一个问题,用户线程正在执行,其中的引用类型对象的内存地址却被修改了,用户线程如何感知到这种变化并立刻调整呢?

# 用户线程如何在GC时实时修改引用对象的内存地址

  1. 写屏障 可行但不推荐,通过写屏障,我们可以在gc线程修改内存地址的时候,实时的修改用户线程的对象的内存地址,这样gc线程在清理垃圾的时候,用户线程也能并发执行,但是写屏障成本很高,如果使用写屏障,可能性能还不如暂停用户线程呢。

  2. 使用转发指针

# Shenandoah使用转发指针的流程

  • 分配新空间:GC线程在目标区域分配一块新的内存。
  • 复制对象数据:GC线程将整个旧对象(包括对象头、实例数据等)逐字节复制到新位置。
  • 设置转发指针:GC线程通过原子操作(如CAS)更新旧对象头中的转发指针字段,使其指向新地址。
  • 后续清理:只有在确保所有线程都不会再访问旧对象后,旧内存才会被回收。

# Shenandoah如何回收上一步提到的旧对象的内存呢?

  • 在并发更新引用阶段,系统性地更新所有根引用和对象引用,使其直接指向新对象。
  • 使用读屏障和写屏障来捕获并发更新引用阶段可能遗漏的引用(例如,在更新过程中新产生的引用)。
  • 在进入并发清理阶段之前,确保所有线程都经历了屏障(通过STW确保所有线程都经历了屏障),并且不再持有旧对象的引用。

# 低延迟收集器(ZGC)

ZGC收集器是一款基于Region内存布局的,不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

# ZGC的堆内存布局

与Shenandoah和G1一样,ZGC也采用基于Region的堆内存布局,但与它们不同的是,ZGC的Region具有动态性——动态创建和销毁,以及动态的区域容量大小。ZGC的Region可以可以分为大、中、小三类容量:

  • 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
  • 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
  • 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作“大型Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实现中是不会被重分配(重分配是ZGC的一种处理动作,用于复制对象的收集器阶段)的,因为复制一个大对象的代价非常高昂。

# ZGC的并发整理算法实现

ZGC收集器有一个标志性的设计是它采用的染色指针技术(Colored Pointer)。从前,如果我们要在对象上存储一些额外的、只供收集器或者虚拟机本身使用的数据,通常会在对象头中增加额外的存储字段,如对象的哈希码、分代年龄、锁记录等就是这样存储的。这种记录方式在有对象访问的场景下是很自然流畅的,不会有什么额外负担。但如果对象存在被移动过的可能性,即不能保证对象访问能够成功呢?又或者有一些根本就不会去访问对象,但又希望得知该对象的某些信息的应用场景呢?能不能从指针或者与对象内存无关的地方得到这些信息,譬如是否能够看出来对象被移动过?这样的要求并非不合理的刁难,先不去说并发移动对象可能带来的可访问性问题,此前我们就遇到过这样的要求——追踪式收集算法的标记阶段就可能存在只跟指针打交道而不必涉及指针所引用的对象本身的场景。例如对象标记的过程中需要给对象打上三色标记,这些标记本质上就只和对象的引用有关,而与对象本身无关——某个对象只有它的引用关系能决定它存活与否,对象上其他所有的属性都不能够影响它的存活判定结果。HotSpot虚拟机的几种收集器有不同的标记实现方案,有的把标记直接记录在对象头上(如Serial收集器),有的把标记记录在与对象相互独立的数据结构上(如G1、Shenandoah使用了一种相当于堆内存的1/64大小的,称为BitMap的结构来记录标记信息),而ZGC的染色指针是最直接的、最纯粹的,它直接把标记信息记在引用对象的指针上,这时,与其说可达性分析是遍历对象图来标记对象,还不如说是遍历“引用图”来标记“引用”了。

什么是染色指针

染色指针是一种直接将少量额外的信息存储在指针上的技术,可是为什么指针本身也可以存储额外信息呢?在64位系统中,理论可以访问的内存高达16EB(2的64次幂)字节。实际上,基于需求(用不到那么多内存)、性能(地址越宽在做地址转换时需要的页表级数越多)和成本(消耗更多晶体管)的考虑,在AMD64架构中只支持到52位(4PB)的地址总线和48位(256TB)的虚拟地址空间,所以目前64位的硬件实际能够支持的最大内存只有256TB。此外,操作系统一侧也还会施加自己的约束,64位的Linux则分别支持47位(128TB)的进程虚拟地址空间和46位(64TB)的物理地址空间,64位的Windows系统甚至只支持44位(16TB)的物理地址空间。尽管Linux下64位指针的高18位不能用来寻址,但剩余的46位指针所能支持的64TB内存在今天仍然能够充分满足大型服务器的需要。鉴于此,ZGC的染色指针技术继续盯上了这剩下的46位指针宽度,将其高4位提取出来存储四个标志信息。通过这些标志位,虚拟机可以直接从指针中看到其引用对象的三色标记状态是否进入了重分配集(即被移动过)、是否只能通过finalize()方法才能被访问到。当然,由于这些标志位进一步压缩了原本就只有46位的地址空间,也直接导致ZGC能够管理的内存不可以超过4TB(2的42次幂)。


染色指针的三大优势

  • 染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理。这点相比起Shenandoah是一个颇大的优势,使得理论上只要还有一个空闲Region,ZGC就能完成收集,而Shenandoah需要等到引用更新阶段结束以后才能释放回收集中的Region,这意味着堆中几乎所有对象都存活的极端情况,需要1∶1复制对象到新Region的话,就必须要有一半的空闲Region来完成收集。
  • 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,设置内存屏障,尤其是写屏障的目的通常是为了记录对象引用的变动情况,如果将这些信息直接维护在指针中,显然就可以省去一些专门的记录操作。实际上,到目前为止ZGC都并未使用任何写屏障,只使用了读屏障(一部分是染色指针的功劳,一部分是ZGC现在还不支持分代收集,天然就没有跨代引用的问题)。内存屏障对程序运行时性能的损耗在前面章节中已经讲解过,能够省去一部分的内存屏障,显然对程序运行效率是大有裨益的,所以ZGC对吞吐量的影响也相对较低。
  • 染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。现在Linux下的64位指针还有前18位并未使用,它们虽然不能用来寻址,却可以通过其他手段用于信息记录。如果开发了这18位,既可以腾出已用的4个标志位,将ZGC可支持的最大堆内存从4TB拓展到64TB,也可以利用其余位置再存储更多的标志,譬如存储一些追踪信息来让垃圾收集器在移动对象时能将低频次使用的对象移动到不常访问的内存区域。

# ZGC的运作流程

ZGC的运作过程大致可划分为并发标记、并发预备重分配、并发重分配、并发重映射四个阶段,这四个阶段都是可以并发执行的,仅是两个阶段中间会存在短暂的停顿小阶段,这些小阶段,譬如初始化GC Root直接关联对象的Mark Start,与之前G1和Shenandoah的Initial Mark阶段并没有什么差异。

  • 并发标记(Concurrent Mark):与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的阶段,前后也要经过类似于G1、Shenandoah的初始标记、最终标记的短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的。与G1、Shenandoah不同的是,ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1标志位。
  • 并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。
  • 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(ForwardTable),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”能力。还有另外一个直接的好处是由于染色指针的存在,一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配(但是转发表还得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的。
  • 并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用。

ZGC中如何并发引用更新

  1. 在用户线程执行的过程中GC线程遍历对象图并更新GC Root中的引用关系,将指向一个对象的旧地址更新为新地址
  2. 如果在步骤1中有线程访问对象,刚好触发读屏障,直接更新为新地址
  3. 经过步骤1和2,就能确保对象图中旧地址都改为了新地址了
  4. 在步骤1和2搞完之后,还需要做一个STW,遍历所有线程和寄存器的GC Root,如果有旧地址,更新为新地址(这个不必深究,只能说并发过程中可能会在GCRoot中存有旧地址)
  5. 经过步骤4之后,就能确保旧地址都改为新地址了,也就可以释放旧地址中的对象了

# 内存分配和回收策略

Java中,新生代和老年代共享堆内存,默认情况下,老年代的空间是新生代的两倍,新生代中又分为Eden,Survivor From,Survivor To,默认情况下Eden:Survivor From:Survivor To = 8:1:1,假定现在堆内存是6G,那么新生代占用2G,老年代占用4G,Eden占用1.6G,Survivor From和Survivor To分别占用0.2G。

# 对象优先在Eden分配

这个没有什么值得特殊说明的,一个新对象创建后,默认放在新生代的Eden区。

# 大对象直接进入老年代

大对象要么被回收,要么就一直存在,根据上面的Survivor分区在6G的内存中仅占用0.2G就可以知道这个区只占总堆内存的3%,如果某个大对象短期内不被回收,那它在新生代中执行标记-整理的成本就很高(要在Survivor From和Survivor To来回倒腾),还不如直接把它扔到老年代中等待后续FullGC回收老年代的时候把它清理掉呢。
通过指定-XX:PretenureSizeThreshold参数可以设置大对象的大小,超过这个数值的对象都是大对象,会直接分配到老年代。

# 长期存活的对象将进入老年代

经过多次新生代GC之后还能存活(达到分代年龄)的对象会调整到老年代中存储。

# 动态对象年龄判定

如果Survivor中相同分代年龄的对象的总和大于Survivor的一半,那么这一次回收中就会把这个年龄或以上年龄的对象放到老年代。这个调整会影响后续的GC,后续GC同样会根据Survivor的使用率做动态调整。

# 空间分配担保

由上面Eden,Survior可以看到,两者是8比1,完全存在Eden中无法回收的对象超过Survivor的情况,这个时候就需要空间分配担保了(使用老年代临时存储Eden中未回收的对象)。

# JDK8中我们如何选择垃圾回收器的组合

  • 新生代使用Serial、老年代使用Serial Old

  • 新生代使用ParNew、老年代使用CMS

  • 新生代使用Parallel Scavenge、老年代使用Parallel Old jdk8中默认的组合

  • 使用G1

不推荐使用的组合

以下两种组合虽然在jdk8中可以使用,但是在jdk9中明令禁止,因此不推荐使用(即使不禁止也没人这样用,因为性能很差)。

  1. 新生代使用Serial、老年代使用CMS
  2. 新生代使用ParNew、老年代使用SerialOld

# 虚拟机性能监控及故障处理

# 基础故障处理工具

有以下基础故障分析工具可以使用,分别是jps(获取java进程号,后续其他指令如jstat,jinfo,jmap,jstack均依赖jps获取到的jvm进程号),jinfo(查询以及修改配置信息),jstat(监控GC),jmap(生成堆转储heapdump文件),jhat(分析heapdump文件),jstack(生成线程快照)。

# 1)jps:获取jvm进程id

    jps(JVM Process Status Tool)指令与操作系统中的ps指令一样,都可以用来查看java进程的相关信息。一般从运维的角度来看,我们都是通过ps auxww | grep java 这条指令查找java进程的,其实用jps指令效果会更好。
    jps的实现原理很简单,在进程运行的时候,悄咪咪的在操作系统的临时目录里将相关信息写入,执行jps指令的过程实际上就是读取文件信息的过程。

# jps一次只支持一个参数
[root@VM-24-3-centos ~]# jps -q
3425974
[root@VM-24-3-centos ~]# jps -l
3426024 sun.tools.jps.Jps
[root@VM-24-3-centos ~]# jps -m
3426098 Jps -m
[root@VM-24-3-centos ~]# jps -v
3426169 Jps -Denv.class.path=.:/apptools/jdk1.8.0_151/lib/dt.jar:/apptools/jdk1.8.0_151/lib/tools.jar -Dapplication.home=/apptools/jdk1.8.0_151 -Xms8m

[root@hkdcl215156 ~]# jps -v
2625 Elasticsearch -Xms1g -Xmx1g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Djava.io.tmpdir=/tmp/elasticsearch.xLU3InmU -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=data -XX:ErrorFile=logs/hs_err_pid%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=32 -XX:GCLogFileSize=64m -Des.path.home=/app/elasticsearch-6.5.1 -Des.path.conf=/app/elasticsearch-6.5.1/config -Des.distribution.flavor=default -Des.distribution.type=tar
8979 Bootstrap -Djava.net.preferIPv4Stack=true -Xms512m -Xmx512m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8 -javaagent:/app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/boot/jetty-alpn-agent-2.0.9.jar -Dvertx.disableFileCaching=true -Dvertx.disableFileCPResolving=true -Dvertx.disableContextTiming=true -Dvertx.disableWebsockets=true -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory
3846 Bootstrap -Djava.net.preferIPv4Stack=true -Xms1024m -Xmx1024m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8
8009 Bootstrap -Djava.net.preferIPv4Stack=true -Xms1024m -Xmx1024m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8
442796 jar -Xms512m -Xmx512m
10827 Jps -Dapplication.home=/app/lib/jdk1.8.0_92 -Xms8m
4827 Bootstrap -Djava.net.preferIPv4Stack=true -Xms1024m -Xmx2048m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8 -Dgravitee.home=/app/gravitee/graviteeio-full-1.25.1/graviteeio-management-api-1.25.1 -Dvertx.disableFileCaching=true -Dvertx.disableFileCPResolving=true -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory

# 2)jinfo:查看Java配置信息

# 打印jinfo的帮助文档
[root@hkdcl215156 ~]# jinfo --help
Usage:
    jinfo [option] <pid>
        (to connect to running process 查看某个正在运行的java进程的配置信息)
    jinfo [option] <executable <core>
        (to connect to a core file 查看某个核心文件的配置信息)
    jinfo [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server 查看某个开启了debug运行模式的远程服务进程的配置信息)

where <option> is one of:
    # 查看某个属性的值
    -flag <name>         to print the value of the named VM flag
    # 启用/禁用某个属性
    -flag [+|-]<name>    to enable or disable the named VM flag
    # 设置某个属性的值
    -flag <name>=<value> to set the named VM flag to the given value
    # 打印所有能查看的属性信息
    -flags               to print VM flags
    # 打印系统属性
    -sysprops            to print Java system properties
    # 打印所有能打印的信息
    <no option>          to print both of the above
    # 打印帮助文档
    -h | -help           to print this help message
查看详情
# 打印所有能打印的信息
[root@hkdcl215156 ~]# jinfo 8979
Attaching to process ID 8979, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.92-b14
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.92-b14
sun.boot.library.path = /app/lib/jdk1.8.0_92/jre/lib/amd64
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = US
user.dir = /app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_92-b14
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /app/lib/jdk1.8.0_92/jre/lib/endorsed
gravitee.home = /app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1
line.separator = 

java.io.tmpdir = /tmp
java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
io.netty.noJdkZlibDecoder = false
sun.nio.ch.bugLevel = 
java.class.version = 52.0
java.specification.name = Java Platform API Specification
java.net.preferIPv4Stack = true
vertx.disableFileCPResolving = true
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 4.19.90-24.4.v2101.ky10.x86_64
user.home = /root
user.timezone = Asia/Hong_Kong
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
vertx.disableFileCaching = true
user.name = root
java.class.path = /app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/lib/gravitee-gateway-standalone-bootstrap-1.25.1.jar:/app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/boot/jetty-alpn-agent-2.0.9.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = io.gravitee.gateway.standalone.boostrap.Bootstrap
java.home = /app/lib/jdk1.8.0_92/jre
user.language = en
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
vertx.logger-delegate-factory-class-name = io.vertx.core.logging.SLF4JLogDelegateFactory
java.version = 1.8.0_92
java.ext.dirs = /app/lib/jdk1.8.0_92/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /app/lib/jdk1.8.0_92/jre/lib/resources.jar:/app/lib/jdk1.8.0_92/jre/lib/rt.jar:/app/lib/jdk1.8.0_92/jre/lib/sunrsasign.jar:/app/lib/jdk1.8.0_92/jre/lib/jsse.jar:/app/lib/jdk1.8.0_92/jre/lib/jce.jar:/app/lib/jdk1.8.0_92/jre/lib/charsets.jar:/app/lib/jdk1.8.0_92/jre/lib/jfr.jar:/app/lib/jdk1.8.0_92/jre/classes
java.awt.headless = true
logback.configurationFile = /app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/config/logback.xml
java.vendor = Oracle Corporation
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
gravitee.conf = /app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/config/gravitee.yml
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
vertx.disableWebsockets = true
vertx.disableContextTiming = true
sun.cpu.isalist = 

VM Flags:
Non-default VM flags: -XX:CICompilerCount=3 -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:MaxNewSize=178782208 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=178782208 -XX:OldSize=358088704 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
Command line:  -Djava.net.preferIPv4Stack=true -Xms512m -Xmx512m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8 -javaagent:/app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/boot/jetty-alpn-agent-2.0.9.jar -Dvertx.disableFileCaching=true -Dvertx.disableFileCPResolving=true -Dvertx.disableContextTiming=true -Dvertx.disableWebsockets=true -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory
# 打印所有的flags
[root@hkdcl215156 ~]# jinfo -flags 8979
Attaching to process ID 8979, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.92-b14
Non-default VM flags: -XX:CICompilerCount=3 -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:MaxNewSize=178782208 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=178782208 -XX:OldSize=358088704 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
Command line:  -Djava.net.preferIPv4Stack=true -Xms512m -Xmx512m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8 -javaagent:/app/gravitee/graviteeio-full-1.25.1/graviteeio-gateway-1.25.1/boot/jetty-alpn-agent-2.0.9.jar -Dvertx.disableFileCaching=true -Dvertx.disableFileCPResolving=true -Dvertx.disableContextTiming=true -Dvertx.disableWebsockets=true -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory
# 打印某一个特定key属性的value值
[root@hkdcl215156 ~]# jinfo -flag CMSInitiatingOccupancyFraction 8979
-XX:CMSInitiatingOccupancyFraction=-1

# 3)jstat:虚拟机实时监控

    jstat主要监视类加载,即时编译,以及GC这三块的内容。它的用法如下:

jstat -option pid 频率 次数

    其中涉及的操作option如下:

选项 说明
-class 监视类加载,卸载数量,总空间以及类加载所消耗的时间。
-compiler 输出即时编译器编译过的方法、耗时等信息。
-printcompilation 输出已经被即时编译的方法。
-gc 监视java堆情况,包括Eden区,2个Survivor区,老年代,元空间的容量,已用空间,垃圾收集时间合计等信息。
-gccapacity 与-gc类似,输出主要关注Java堆各个区域使用到的最大,最小空间。
-gcutil 与-gc类似,输出主要关注已使用空间占总空间的百分比。
-gccause 与-gcutil功能一致,会额外输出导致上一次垃圾收集产生的原因。
-gcnew 监视新生代垃圾收集情况。
-gcnewapacity 与-gcnew类似,输出主要关注Java堆新生代使用到的最大,最小空间。
-gcold 监视老年代垃圾收集情况。
-gcoldapacity 与-gcold类似,输出主要关注Java堆老年代使用到的最大,最小空间。
-gcmetacapacity 输出元空间使用的最大以及最小空间。
# 查看进程的类加载情况,每500ms查询一次,一共查询2次
[root@hkdcl215156 ~]# jstat -class 8979 500 2
Loaded  Bytes  Unloaded  Bytes     Time   
 16489 31194.4        7     6.5      10.65
 16489 31194.4        7     6.5      10.65
# 查看进程的垃圾回收情况,每500ms查询一次,一共查询2次
[root@hkdcl215156 ~]# jstat -gc 8979 500 2
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
512.0  512.0  160.0   0.0   173568.0 159855.2  349696.0   153482.5  163328.0 96514.4 13568.0 11335.5  20022  124.132   4      0.473  124.606
512.0  512.0  160.0   0.0   173568.0 159929.2  349696.0   153482.5  163328.0 96514.4 13568.0 11335.5  20022  124.132   4      0.473  124.606

# 4)jmap:Java内存映像工具

    jmap(Memory Map for Java)命令可用于生产堆栈储(heapdump)文件(文件以.hprof结尾),查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。jmap的用法如下:

jmap -option pid

    其中涉及的操作option如下:

选项 说明
-dump 生成堆转储快照文件。
-F 当虚拟机进程对-dump指令没有响应的时候可以使用-F选项强制生成heapdump文件。
-heap 显示Java堆详细信息,如使用了哪种收集器,参数配置,分代情况等。
-histo 显示堆中对象统计信息,包括类,实例数量,合计容量。
-permstat 以ClassLoader(类加载器)为统计口径显示永久代内存状况。
-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize()方法的对象。
查看详情
# 通过jmap -dump生成堆转储快照,生成的堆转储文件包含两块,一块是堆信息,另外一块是线程堆信息(jstack)。
[root@hkdcl215156 ~]# jmap -dump:format=b,file=/mydump.hprof 8979
Dumping heap to /mydump.hprof ...
Heap dump file created
[root@hkdcl215156 ~]# cd /
[root@hkdcl215156 /]# ll -h | grep *.hprof
# 这里可以看到生成了一个421M的文件,后续可以用jhat命令分析这个文件
-rw-------    1 root    root    421M Feb 22 09:24 mydump.hprof
# 通过jamp -heap打印java堆详细信息
[root@hkdcl215156 ~]# jmap -heap 8979
Attaching to process ID 8979, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.92-b14

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 536870912 (512.0MB)
   NewSize                  = 178782208 (170.5MB)
   MaxNewSize               = 178782208 (170.5MB)
   OldSize                  = 358088704 (341.5MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
# 伊甸
Eden Space:
   # 分配容量
   capacity = 177733632 (169.5MB)
   # 使用容量
   used     = 117778632 (112.32245635986328MB)
   # 剩余容量
   free     = 59955000 (57.17754364013672MB)
   # 使用百分比
   66.26693590552406% used
# S0/S1其中一个
From Space:
   capacity = 524288 (0.5MB)
   used     = 163840 (0.15625MB)
   free     = 360448 (0.34375MB)
   31.25% used
# S0/S1其中一个
To Space:
   capacity = 524288 (0.5MB)
   used     = 0 (0.0MB)
   free     = 524288 (0.5MB)
   0.0% used
# 老年代 2G
PS Old Generation
   capacity = 358088704 (341.5MB)
   used     = 157395456 (150.10400390625MB)
   free     = 200693248 (191.39599609375MB)
   43.954320323938504% used

22492 interned Strings occupying 2149392 bytes.

# 5)jhat:虚拟机堆转储快照分析工具

jhat指令可用来分析jmap指令生成的堆转储文件,使用方式是jhat filename,分析完成后,jhat会生成相应的html页面,同时启动一个web服务,服务默认占用7000端口,使用http://localhost:7000/就可以查看相应的分析页面。一般不推荐使用jhat工具去分析,优先使用Memory Analyzer Tool,JProfiler等工具去分析。

# 6)jstack:Java栈区跟踪

    jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈,就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。jstack的用法如下:

jstack -option pid

    其中涉及的操作option如下:

选项 说明
-F 当正常输出的请求不被响应的时候,强制输出线程堆栈。
-l 除堆栈外,额外显示锁的相关信息。
-m 如果调用到本地方法的话,可以额外显示C/C++的堆栈。
查看详情
# 生成线程栈
[root@hkdcl215156 ~]# jstack 8979 > /jstack.log
# 生成线程栈(只输出本地方法栈)
[root@hkdcl215156 /]# jstack -m 8979 > /jstackm.log
# 生成线程栈(额外输出锁相关信息)
[root@hkdcl215156 /]# jstack -l 8979 > /jstackl.log
[root@hkdcl215156 ~]# cd /
[root@hkdcl215156 /]# ll
-rw-r--r--    1 root    root        48113 Feb 22 09:41 jstackl.log
-rw-r--r--    1 root    root        45887 Feb 22 09:47 jstack.log
-rw-r--r--    1 root    root          170 Feb 22 09:41 jstackm.log

# jstack打印信息如下,这里一共有59个线程
2024-02-22 09:47:20
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.92-b14 mixed mode):

"Attach Listener" #59 daemon prio=9 os_prio=0 tid=0x00007fb114003000 nid=0x605c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"logback-8" #58 daemon prio=5 os_prio=0 tid=0x00007fb100013000 nid=0xbb085 waiting on condition [0x00007fb0b63f4000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-7" #57 daemon prio=5 os_prio=0 tid=0x00007fb100011800 nid=0xbb084 waiting on condition [0x00007fb0b64f5000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-6" #56 daemon prio=5 os_prio=0 tid=0x00007fb10000f800 nid=0x87c25 waiting on condition [0x00007fb0b85f8000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-5" #55 daemon prio=5 os_prio=0 tid=0x00007fb100009000 nid=0x87c24 waiting on condition [0x00007fb0b86f9000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-4" #54 daemon prio=5 os_prio=0 tid=0x00007fb10000c000 nid=0x50184 waiting on condition [0x00007fb0ba9fc000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-3" #53 daemon prio=5 os_prio=0 tid=0x00007fb10000a800 nid=0x50180 waiting on condition [0x00007fb0baafd000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-2" #52 daemon prio=5 os_prio=0 tid=0x00007fb100007800 nid=0x19f5a waiting on condition [0x00007fb0babfe000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"logback-1" #51 daemon prio=5 os_prio=0 tid=0x00007fb100008000 nid=0x19f58 waiting on condition [0x00007fb1181c7000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4c8be80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"apikeys-refresher-2" #50 prio=5 os_prio=0 tid=0x00007fb0d410b000 nid=0x2359 waiting on condition [0x00007fb119ac8000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e11268c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"subscriptions-refresher-2" #49 prio=5 os_prio=0 tid=0x00007fb0d4102000 nid=0x2358 waiting on condition [0x00007fb119bc9000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e114e5e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"subscriptions-refresher-1" #48 prio=5 os_prio=0 tid=0x00007fb0f4005800 nid=0x2357 waiting on condition [0x00007fb119eca000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e114e5e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"apikeys-refresher-1" #47 prio=5 os_prio=0 tid=0x00007fb100002800 nid=0x2356 waiting on condition [0x00007fb119fcb000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e11268c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"apikeys-refresher-0" #46 prio=5 os_prio=0 tid=0x00007fb0d40d6800 nid=0x2355 waiting on condition [0x00007fb11a0cc000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e11268c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"subscriptions-refresher-0" #45 prio=5 os_prio=0 tid=0x00007fb0d40d4000 nid=0x2354 waiting on condition [0x00007fb13c10e000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e114e5e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"RxComputationThreadPool-1" #44 daemon prio=5 os_prio=0 tid=0x00007fb0e81d9800 nid=0x2353 waiting on condition [0x00007fb13c213000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e25c6a38> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"RxCachedWorkerPoolEvictor-1" #43 daemon prio=5 os_prio=0 tid=0x00007fb0e8138800 nid=0x2352 waiting on condition [0x00007fb13c314000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e25b9f80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"RxSchedulerPurge-1" #42 daemon prio=5 os_prio=0 tid=0x00007fb0e8137800 nid=0x2351 waiting on condition [0x00007fb13c415000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e25c7428> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"org.eclipse.jetty.util.RolloverFileOutputStream" #41 daemon prio=5 os_prio=0 tid=0x00007fb0e8002800 nid=0x234c in Object.wait() [0x00007fb13c916000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	at java.util.TimerThread.mainLoop(Timer.java:552)
	- locked <0x00000000e0cd7538> (a java.util.TaskQueue)
	at java.util.TimerThread.run(Timer.java:505)

"vert.x-worker-thread-0" #40 prio=5 os_prio=0 tid=0x00007fb0ec009000 nid=0x234b waiting on condition [0x00007fb13ca17000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e01489b0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"DestroyJavaVM" #39 prio=5 os_prio=0 tid=0x00007fb15c00b000 nid=0x2315 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"gateway-monitor" #37 prio=5 os_prio=0 tid=0x00007fb15dda4000 nid=0x234a waiting on condition [0x00007fb13cb18000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e0e8cf60> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"node-monitor" #36 prio=5 os_prio=0 tid=0x00007fb15dd38800 nid=0x2348 waiting on condition [0x00007fb13ce19000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e0f099c8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"sync-1" #35 prio=5 os_prio=0 tid=0x00007fb15dd2e000 nid=0x2347 waiting on condition [0x00007fb13d1fd000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e11ec040> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"Statistics Thread-__DEFAULT__-1" #33 daemon prio=5 os_prio=0 tid=0x00007fb15da9d800 nid=0x2345 waiting on condition [0x00007fb13d2fe000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e1016518> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"__DEFAULT__" #32 daemon prio=5 os_prio=0 tid=0x00007fb15da93000 nid=0x2344 in Object.wait() [0x00007fb13d6d8000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e1075da8> (a java.util.TaskQueue)
	at java.lang.Object.wait(Object.java:502)
	at java.util.TimerThread.mainLoop(Timer.java:526)
	- locked <0x00000000e1075da8> (a java.util.TaskQueue)
	at java.util.TimerThread.run(Timer.java:505)

"Statistics Thread-__DEFAULT__-1" #30 daemon prio=5 os_prio=0 tid=0x00007fb15da45000 nid=0x2342 waiting on condition [0x00007fb13d7d9000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e0f02a20> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"__DEFAULT__" #29 daemon prio=5 os_prio=0 tid=0x00007fb15d876800 nid=0x2341 in Object.wait() [0x00007fb13dbb3000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e0f39920> (a java.util.TaskQueue)
	at java.lang.Object.wait(Object.java:502)
	at java.util.TimerThread.mainLoop(Timer.java:526)
	- locked <0x00000000e0f39920> (a java.util.TaskQueue)
	at java.util.TimerThread.run(Timer.java:505)

"Statistics Thread-__DEFAULT__-1" #27 daemon prio=5 os_prio=0 tid=0x00007fb15c416800 nid=0x233f waiting on condition [0x00007fb13dcb4000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e1188ff0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"__DEFAULT__" #26 daemon prio=5 os_prio=0 tid=0x00007fb15d72f800 nid=0x233e in Object.wait() [0x00007fb13ddb5000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e11e6be8> (a java.util.TaskQueue)
	at java.lang.Object.wait(Object.java:502)
	at java.util.TimerThread.mainLoop(Timer.java:526)
	- locked <0x00000000e11e6be8> (a java.util.TaskQueue)
	at java.util.TimerThread.run(Timer.java:505)

"CleanCursors-2-thread-1" #25 daemon prio=5 os_prio=0 tid=0x00007fb15d5df800 nid=0x233c waiting on condition [0x00007fb13e18d000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e166eb38> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"cluster-ClusterId{value='65bc914866cd4b23131a01e2', description='gravitee.io'}-10.6.215.156:27017" #24 daemon prio=5 os_prio=0 tid=0x00007fb15d5e4000 nid=0x233b waiting on condition [0x00007fb13e28e000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e1675b50> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.waitForSignalOrTimeout(DefaultServerMonitor.java:229)
	at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.waitForNext(DefaultServerMonitor.java:210)
	at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:157)
	- locked <0x00000000e1675718> (a com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable)
	at java.lang.Thread.run(Thread.java:745)

"CleanCursors-1-thread-1" #23 daemon prio=5 os_prio=0 tid=0x00007fb15cffb800 nid=0x2330 waiting on condition [0x00007fb13eda2000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e0a22870> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
	at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

"cluster-ClusterId{value='65bc914766cd4b23131a01e1', description='gravitee.io'}-10.6.215.156:27017" #22 daemon prio=5 os_prio=0 tid=0x00007fb15cfe9800 nid=0x232f waiting on condition [0x00007fb13eea3000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e0a36f68> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.waitForSignalOrTimeout(DefaultServerMonitor.java:229)
	at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.waitForNext(DefaultServerMonitor.java:210)
	at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:157)
	- locked <0x00000000e0a36a70> (a com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-acceptor-thread-0" #21 prio=5 os_prio=0 tid=0x00007fb0f00a7800 nid=0x232e runnable [0x00007fb13f3ed000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e0226c40> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e025eb88> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e025eab0> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-7" #20 prio=5 os_prio=0 tid=0x00007fb15d3e8000 nid=0x232d runnable [0x00007fb13f8f7000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e0222fd8> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e02240d8> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e0224000> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-6" #19 prio=5 os_prio=0 tid=0x00007fb15d3e6800 nid=0x232c runnable [0x00007fb13f9f8000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e014d118> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e01647f8> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e0164710> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-5" #18 prio=5 os_prio=0 tid=0x00007fb15d3e4800 nid=0x232b runnable [0x00007fb13faf9000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e0167050> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e0168150> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e0168078> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-4" #17 prio=5 os_prio=0 tid=0x00007fb15d3e2800 nid=0x232a runnable [0x00007fb13fbfa000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e016a998> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e01a24c8> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e01a23f0> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-3" #16 prio=5 os_prio=0 tid=0x00007fb15d3e1000 nid=0x2329 runnable [0x00007fb13fcfb000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e01a4d10> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e01a5e10> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e01a5d38> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-2" #15 prio=5 os_prio=0 tid=0x00007fb15d3df000 nid=0x2328 runnable [0x00007fb13fdfc000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e01a8658> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e01e15e0> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e01e1508> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-1" #14 prio=5 os_prio=0 tid=0x00007fb15d3dd800 nid=0x2327 runnable [0x00007fb13fefd000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e01e3e28> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e01e4f28> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e01e4e50> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vert.x-eventloop-thread-0" #13 prio=5 os_prio=0 tid=0x00007fb15d3dc000 nid=0x2326 runnable [0x00007fb13fffe000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e01e7758> (a io.netty.channel.nio.SelectedSelectionKeySet)
	- locked <0x00000000e02207a8> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e02206d0> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
	at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:753)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)

"vertx-blocked-thread-checker" #12 daemon prio=5 os_prio=0 tid=0x00007fb15cb9f800 nid=0x2325 in Object.wait() [0x00007fb144b94000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	at java.util.TimerThread.mainLoop(Timer.java:552)
	- locked <0x00000000e00167d0> (a java.util.TaskQueue)
	at java.util.TimerThread.run(Timer.java:505)

"AsyncAppender-Worker-async-console" #10 daemon prio=5 os_prio=0 tid=0x00007fb15c7da800 nid=0x2324 waiting on condition [0x00007fb145d24000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e00169c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
	at ch.qos.logback.core.AsyncAppenderBase$Worker.run(AsyncAppenderBase.java:289)

"AsyncAppender-Worker-async-file" #9 daemon prio=5 os_prio=0 tid=0x00007fb15c7d9800 nid=0x2323 waiting on condition [0x00007fb145e25000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e0016c00> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
	at ch.qos.logback.core.AsyncAppenderBase$Worker.run(AsyncAppenderBase.java:289)

"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007fb15c1eb000 nid=0x2321 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007fb15c1e7800 nid=0x2320 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fb15c1e5000 nid=0x231f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fb15c1e4000 nid=0x231e waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fb15c17f000 nid=0x231d runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fb15c14c000 nid=0x231c in Object.wait() [0x00007fb1465f4000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
	- locked <0x00000000e0022850> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fb15c147800 nid=0x231b in Object.wait() [0x00007fb1466f5000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x00000000e0022a08> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=0 tid=0x00007fb15c140000 nid=0x231a runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fb15c020800 nid=0x2316 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fb15c022800 nid=0x2317 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fb15c024000 nid=0x2318 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fb15c026000 nid=0x2319 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007fb15c1f8000 nid=0x2322 waiting on condition 

JNI global references: 2350
jvm问题分析流程


# 实时监控jvm的可视化工具

  1. 如果需要实时监控JVM,我们需要在启动JVM的时候开启JMX的功能,开启了之后就可以通过JConsole,VisualVM等客户端工具实时监控JVM了。
  2. 如果我们的jvm目前已经在运行,且未开启JMX的功能,如果需要实时分析的话,可以选择在jvm所在主机临时安装Arthas并启动,通过Arthas的客户端工具分析JVM,需要特别注意,问题分析完毕后必须停止Arthas的运行,不允许Arthas长期在生产环境运行。

# 其他工具

# JProfiler

JProfiler是heapdump分析工具,使用JProfiler工具打开.hprof文件,可以看到两块内容,分别是Current Object Set(当前堆的所有对象情况)以及Thread Dump(当前堆的线程情况,与jstack指令输出的内容一致)。


# 火焰图:cpu分析工具

通过安装perf软件包,可以使用这个软件,对cpu进行监控,生成监控文件。生成的监控文件并不直观,因此我们可以把文件转换成图标的方式去分析,也就是火焰图,具体使用的工具是Flame Graph。

# Arthas(阿尔萨斯):线上问题诊断工具

Arthas是阿里巴巴开源的一个线上问题诊断工具,可以简单理解为一个web服务+全家桶工具,如果你要使用对应的分析工具,工具底层的实现还是基于操作系统和jdk的相关分析工具,只不过在这些基础之上,它提供了Dashboard可以实时查看系统的运行状况,查看方法的入参/返回值/异常,在线热更新代码,快速定位应用的热点,生成火焰图,在线诊断,点开网页诊断线上应用。Arthas-Java 应用诊断利器 (opens new window)

# 类文件结构

# 魔数与Class文件的版本

# 魔数

类文件的前面4个字节分别是CA,FE,BA,BE,连起来是0xCAFEBABE,它的唯一作用是确定这个文件是否为 一个能被虚拟机接受的Class文件。

# Class文件的版本

紧接着魔数,第5到6个字节代表Java的次版本号,默认为0x0000,代表0,第7到8位是主版本号,0x0032, 对应十进制的50,也就是说通过主版本号可以看出对应的是jdk6。

# 常量池

常量池是一个集合,所以前面的2位字节代表常量池中有多少个常量。针对每一个常量,又有一个1字节的标志位,声明常量的类型,而针对每一个常量类型(如整型常量,浮点型常量,UTF8编码的字符串),又有不同的数据结构集合。(这块《深入理解Java虚拟机》针对常量池中的大部分常量的数据结构做了讲解,但是我们没必要一一查看,一般我们直接用javap指令查看class的常量池即可)。

# 访问标志

在常量池结束之后,我们可以看到2个字节长度的访问标志,访问标志以及标志的含义如下:

访问标志 标志值 业务含义
ACC_PUBLIC 0x0001 声明类是否是public
ACC_FINAL 0x0010 声明类是否是final
ACC_SUPER 0x0020 识别类是否是由jdk1.2以上的编译器编译
ACC_INTERFACE 0x0200 声明这是一个接口
ACC_ABSTRACT 0x0400 是否由abstract修饰
ACC_SYNTHETIC 0x1000 标明这个类不是由用户代码产生的
ACC_ANNOTATION 0x2000 标明这是一个注解
ACC_ENUM 0x4000 标明这是一个枚举
ACC_MODULE 0x8000 标明这是一个jdk9中的模块

一个使用jdk8编译的非final声明的public类,他的访问标志为ACC_PUBLIC+ACC_SUPER=0x0001 | 0x0020 = 0x0021,反之如果我们看到0x0021,怎么知道它是一个非final的public类呢? 通过比对上面的表,0x8000,0x4000,0x2000,0x1000,0x0400,0x0200均大于0x0021,因此这个类不是模块,不是枚举,不是注解,是由用户代码产生的,没有经过abstract修饰,也不是接口,那它就是一个普通的类, 0x0021>0x0020,说明它是经过jdk2以上的编译器编译的。接下来就判断它是不是final类,如果是的话,它应该是0x0031了,这里它是0x0021,说明不是final类,同理,如果类没有声明为public,它应该是0x0020,这里它是0x0021,就说明它是public。

# 类索引,父类索引与接口索引集合

# 类索引

类索引是一个2字节的数据,指向的是常量池中的常量编号,这个编号最终指向的是内容是类的全限定类名。

# 父类索引

父类索引页是一个2字节的数据,执行的是常量池中的常量编号,这个编号最终指向的是内容是类的父类的全限定类名。

# 接口索引集合

接口索引集合由一个2字节的计数器以及后面的数据集组成,我们应该有能力猜到,如果计数器为0,说明类没有声明接口实现,如果不为0,说明后面跟着一些2字节的数据,数据指向的是常量池的常量编号,最终指向的是接口的全限定名称。

# 字段表集合

Java类中的属性的集合。如private String name = "howl";字段表集合里首先会有一个计数器,显示了这个类一共有多少个属性,针对每个属性又有一个数据结构,首先会有access_flag控制类的访问权限,其次会有描述符字段,记录属性的类型,其后会有属性名称以及值的定义。

# 方法表集合

方法表集合与其他集合一致(如字段表),有一个计数器记录类里有多少方法,针对每个方法,对方法访问标志access_flag,返回值,方法名,形参进行记录。对于方法里的具体内容,则是使用了一个名为Code的索引值,指向属性表集合里的Code属性。

# 属性表集合

属性表集合也是由一个计数器后面跟多个属性表的结构,每个属性表同样有它的数据结构,如Code属性用于存储方法的方法体。

# 补充资料

# JDK8默认垃圾回收器是哪个?

# 可以看到,当前主机的jdk版本是1.8.0_92,默认使用的垃圾回收器是UseParallelGC
# ParallelGC对新生代进行YoungGC时使用Parallel Scavenge垃圾回收器
# ParallelGC对老年代进行FullGC时使用Parallel Old垃圾回收器
[root@VM-8-5-centos ~]# java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=30109824 -XX:MaxHeapSize=481757184 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)

# JDK8中GC日志如何查看? 基于jdk8

首先如果想查看GC日志,我们在启动java进程的时候,需要告知jvm打印相关的gc日志。这样在gc的时候,jvm就会将相关日志打印到我们的指定路径下。

    1)查看GC基本信息:

查看详情
# VM参数 -Xmx1g -Xms1g -XX:+PrintGC -Xloggc:d:\\gc\\gc.log
# jdk信息
OpenJDK 64-Bit Server VM (25.382-b05) for windows-amd64 JRE (1.8.0_382-b05), built on Jul 14 2023 19:32:03 by "Administrator" with MS VC++ 12.0 (VS2013)
# 内存信息
Memory: 4k page, physical 16595576k(5893888k free), swap 19085944k(3566920k free)
# 命令行
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
# 0.278 jvm启动到打印这行日志的时间
# GC (Allocation Failure) :说明触发的是YoungGC,GC原因是Allocation Failure (表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了)
# 262144K: gc前堆大小 79933K: gc后堆大小 1005056K:堆内存总大小
# 0.0115979 secs :gc时间
0.278: [GC (Allocation Failure)  262144K->79933K(1005056K), 0.0115979 secs]
0.327: [GC (Allocation Failure)  342077K->149866K(1005056K), 0.0163560 secs]
0.378: [GC (Allocation Failure)  412010K->229232K(1005056K), 0.0242948 secs]
0.439: [GC (Allocation Failure)  491363K->296103K(1005056K), 0.0217281 secs]
0.498: [GC (Allocation Failure)  558247K->377036K(1005056K), 0.0203519 secs]
0.555: [GC (Allocation Failure)  639180K->452762K(859648K), 0.0178402 secs]
0.588: [GC (Allocation Failure)  569374K->490731K(932352K), 0.0088185 secs]
0.613: [GC (Allocation Failure)  607467K->522827K(932352K), 0.0114736 secs]
0.641: [GC (Allocation Failure)  639563K->552987K(932352K), 0.0138871 secs]
0.676: [GC (Allocation Failure)  669723K->580301K(932352K), 0.0187312 secs]
0.710: [GC (Allocation Failure)  697037K->616094K(932352K), 0.0207839 secs]
0.747: [GC (Allocation Failure)  732830K->647615K(932352K), 0.0121539 secs]
0.759: [Full GC (Ergonomics)  647615K->339162K(932352K), 0.0433159 secs]
0.821: [GC (Allocation Failure)  455898K->373798K(932352K), 0.0036993 secs]
0.843: [GC (Allocation Failure)  490534K->407945K(932352K), 0.0067399 secs]
0.866: [GC (Allocation Failure)  524681K->441273K(932352K), 0.0069481 secs]
0.889: [GC (Allocation Failure)  558009K->475437K(932352K), 0.0075319 secs]
0.912: [GC (Allocation Failure)  592173K->509551K(932352K), 0.0071908 secs]
0.937: [GC (Allocation Failure)  626287K->542725K(932352K), 0.0075182 secs]
0.961: [GC (Allocation Failure)  659461K->576139K(932352K), 0.0067951 secs]
0.987: [GC (Allocation Failure)  692875K->609600K(932352K), 0.0092748 secs]
1.021: [GC (Allocation Failure)  726336K->642594K(932352K), 0.0086655 secs]
1.054: [GC (Allocation Failure)  759330K->677239K(932352K), 0.0101295 secs]
1.064: [Full GC (Ergonomics)  677239K->353958K(932352K), 0.0429270 secs]
1.130: [GC (Allocation Failure)  470395K->393757K(932352K), 0.0040754 secs]

    2)查看GC详细信息:

查看详情
# VM参数 -Xmx1g -Xms1g -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:d:\\gc\\gc.log
# jdk版本
OpenJDK 64-Bit Server VM (25.382-b05) for windows-amd64 JRE (1.8.0_382-b05), built on Jul 14 2023 19:32:03 by "Administrator" with MS VC++ 12.0 (VS2013)
# 内存信息
Memory: 4k page, physical 16595576k(5805944k free), swap 19085944k(3607288k free)
# 命令行参数,可以看出这里使用的是jdk8默认的垃圾回收器
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 

# 2024-02-21T11:34:42.353+0800:gc开始时间 0.324:jvm启动到打印这行日志的时间

# [GC (Allocation Failure) [PSYoungGen: 262144K->43518K(305664K)] 262144K->77091K(1005056K), 0.0168742 secs]
## GC (Allocation Failure) 说明触发的是YoungGC,GC原因是Allocation Failure (表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了)

## [PSYoungGen: 262144K->43518K(305664K)] 262144K->77091K(1005056K)
## 第一个262144K:年轻代GC前总内存占用量 43518K:年轻代GC后总内存占用量  305664K:年轻代的总内存大小
## 第二个262144K:总内存占用量(注意此时总内存占用量和young内存占用量是相等的,因为此时老年代还没有对象,所有对象在新生代)
## 77091K:GC后总内存占用量    1005056K:堆内存总大小
## 即年轻代的内存占用减少了262144K-43518K=218626K,即减少了213.5M
## 总内存占用从262144K减少到了77091K,即减少了185053K,即减少了180.7M
## 也就说明有213.5M-180.7M=32.8M的数据转移到了老年代中

## 0.0168742 secs young GC的时长是0.016秒

# [Times: user=0.02 sys=0.03, real=0.02 secs] 
## 这里打印的是GC使用的CPU时间 
2024-02-21T11:34:42.353+0800: 0.324: [GC (Allocation Failure) [PSYoungGen: 262144K->43518K(305664K)] 262144K->77091K(1005056K), 0.0168742 secs] [Times: user=0.02 sys=0.03, real=0.02 secs] 

2024-02-21T11:34:42.417+0800: 0.388: [GC (Allocation Failure) [PSYoungGen: 305662K->43516K(305664K)] 339235K->150299K(1005056K), 0.0224450 secs] [Times: user=0.03 sys=0.13, real=0.02 secs] 
2024-02-21T11:34:42.489+0800: 0.461: [GC (Allocation Failure) [PSYoungGen: 305660K->43518K(305664K)] 412443K->223931K(1005056K), 0.0172017 secs] [Times: user=0.00 sys=0.09, real=0.02 secs] 
2024-02-21T11:34:42.559+0800: 0.530: [GC (Allocation Failure) [PSYoungGen: 305662K->43503K(305664K)] 486075K->291606K(1005056K), 0.0159534 secs] [Times: user=0.08 sys=0.02, real=0.02 secs] 
2024-02-21T11:34:42.628+0800: 0.600: [GC (Allocation Failure) [PSYoungGen: 305313K->43518K(305664K)] 553416K->360748K(1005056K), 0.0179793 secs] [Times: user=0.03 sys=0.05, real=0.02 secs] 
2024-02-21T11:34:42.686+0800: 0.657: [GC (Allocation Failure) [PSYoungGen: 305662K->43517K(160256K)] 622892K->438346K(859648K), 0.0174253 secs] [Times: user=0.00 sys=0.09, real=0.02 secs] 
2024-02-21T11:34:42.727+0800: 0.698: [GC (Allocation Failure) [PSYoungGen: 160253K->70894K(232960K)] 555082K->473434K(932352K), 0.0118594 secs] [Times: user=0.02 sys=0.06, real=0.01 secs] 
2024-02-21T11:34:42.760+0800: 0.731: [GC (Allocation Failure) [PSYoungGen: 187630K->90479K(232960K)] 590170K->502602K(932352K), 0.0098620 secs] [Times: user=0.13 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:42.786+0800: 0.757: [GC (Allocation Failure) [PSYoungGen: 207215K->99410K(232960K)] 619338K->531098K(932352K), 0.0120444 secs] [Times: user=0.13 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:42.816+0800: 0.787: [GC (Allocation Failure) [PSYoungGen: 216146K->76074K(232960K)] 647834K->561451K(932352K), 0.0161529 secs] [Times: user=0.03 sys=0.06, real=0.02 secs] 
2024-02-21T11:34:42.849+0800: 0.821: [GC (Allocation Failure) [PSYoungGen: 192810K->39856K(232960K)] 678187K->595289K(932352K), 0.0131285 secs] [Times: user=0.03 sys=0.09, real=0.01 secs] 
2024-02-21T11:34:42.878+0800: 0.849: [GC (Allocation Failure) [PSYoungGen: 156592K->37330K(232960K)] 712025K->628828K(932352K), 0.0078177 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:42.899+0800: 0.870: [GC (Allocation Failure) [PSYoungGen: 153798K->40450K(232960K)] 745297K->664638K(932352K), 0.0085327 secs] [Times: user=0.03 sys=0.06, real=0.01 secs] 
# 这里触发了Full GC
2024-02-21T11:34:42.908+0800: 0.879: [Full GC (Ergonomics) [PSYoungGen: 40450K->0K(232960K)] [ParOldGen: 624187K->337282K(699392K)] 664638K->337282K(932352K), [Metaspace: 3566K->3566K(1056768K)], 0.0434790 secs] [Times: user=0.20 sys=0.02, real=0.04 secs] 
2024-02-21T11:34:42.970+0800: 0.941: [GC (Allocation Failure) [PSYoungGen: 116201K->39397K(232960K)] 453484K->376680K(932352K), 0.0049786 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2024-02-21T11:34:42.994+0800: 0.966: [GC (Allocation Failure) [PSYoungGen: 155964K->38130K(232960K)] 493246K->411481K(932352K), 0.0079288 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:43.019+0800: 0.990: [GC (Allocation Failure) [PSYoungGen: 154866K->41938K(232960K)] 528217K->449761K(932352K), 0.0087039 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
2024-02-21T11:34:43.044+0800: 1.015: [GC (Allocation Failure) [PSYoungGen: 158674K->43700K(232960K)] 566497K->488591K(932352K), 0.0120657 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:43.076+0800: 1.048: [GC (Allocation Failure) [PSYoungGen: 160436K->38791K(232960K)] 605327K->523471K(932352K), 0.0086779 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:43.107+0800: 1.079: [GC (Allocation Failure) [PSYoungGen: 155527K->35408K(232960K)] 640207K->556562K(932352K), 0.0079792 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:43.138+0800: 1.109: [GC (Allocation Failure) [PSYoungGen: 152144K->42003K(232960K)] 673298K->594580K(932352K), 0.0084023 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2024-02-21T11:34:43.168+0800: 1.140: [GC (Allocation Failure) [PSYoungGen: 158739K->41319K(232960K)] 711316K->632273K(932352K), 0.0093842 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] 
# 堆信息
Heap
## 年轻代
PSYoungGen      total 232960K, used 55613K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
###伊甸园
eden space 116736K, 12% used [0x00000000eab00000,0x00000000eb8f5828,0x00000000f1d00000)
###S0
from space 116224K, 35% used [0x00000000f1d00000,0x00000000f4559e10,0x00000000f8e80000)
###S1
to   space 116224K, 0% used [0x00000000f8e80000,0x00000000f8e80000,0x0000000100000000)
## 老年代
ParOldGen       total 699392K, used 590953K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 84% used [0x00000000c0000000,0x00000000e411a718,0x00000000eab00000)
# 元空间大小
Metaspace       used 4055K, capacity 4572K, committed 4864K, reserved 1056768K
class space    used 439K, capacity 460K, committed 512K, reserved 1048576K

    3)查看GC前后的堆,元空间的可用容量变化:

查看详情
# VM参数 -Xmx1g -Xms1g -XX:+PrintHeapAtGC -Xloggc:d:\\gc\\gc.log
OpenJDK 64-Bit Server VM (25.382-b05) for windows-amd64 JRE (1.8.0_382-b05), built on Jul 14 2023 19:32:03 by "Administrator" with MS VC++ 12.0 (VS2013)
Memory: 4k page, physical 16595576k(6587980k free), swap 19085944k(4172504k free)
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
{
# GC之前    
Heap before GC invocations=1 (full 0):
 PSYoungGen      total 305664K, used 262144K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 100% used [0x00000000eab00000,0x00000000fab00000,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 3565K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 382K, capacity 388K, committed 512K, reserved 1048576K
0.241: [GC (Allocation Failure)  262144K->78361K(1005056K), 0.0121320 secs]
# GC之后
Heap after GC invocations=1 (full 0):
 PSYoungGen      total 305664K, used 43512K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 0% used [0x00000000eab00000,0x00000000eab00000,0x00000000fab00000)
  from space 43520K, 99% used [0x00000000fab00000,0x00000000fd57e0a8,0x00000000fd580000)
  to   space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
 ParOldGen       total 699392K, used 34848K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 4% used [0x00000000c0000000,0x00000000c2208398,0x00000000eab00000)
 Metaspace       used 3565K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 382K, capacity 388K, committed 512K, reserved 1048576K
}

    4)查看GC过程中用户线程并发时间以及停顿的时间:

查看详情
# VM参数 -Xmx1g -Xms1g -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -Xloggc:d:\\gc\\gc.log
OpenJDK 64-Bit Server VM (25.382-b05) for windows-amd64 JRE (1.8.0_382-b05), built on Jul 14 2023 19:32:03 by "Administrator" with MS VC++ 12.0 (VS2013)
Memory: 4k page, physical 16595576k(6794292k free), swap 19085944k(5061560k free)
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
#  应用运行阶段(并发时间) 从上一个安全点结束到当前时刻,应用程序线程不受干扰地连续运行了121.18毫秒。
0.241: Application time: 0.1211784 seconds
# GC暂停时间  0.0105698 (约10.57毫秒)   GC原因 分配失败 - 应用尝试分配对象时,年轻代没有足够空间了。
0.241: [GC (Allocation Failure)  262139K->79293K(1005056K), 0.0105698 secs]
# 总暂停时间: 0.0107120秒 (约10.71毫秒) - 这是应用线程实际停止的总时间
# 安全点暂停阶段(总暂停时间) Stopping threads took: 0.0000281秒 (约0.028毫秒) - 让所有线程进入安全点的时间
0.251: Total time for which application threads were stopped: 0.0107120 seconds, Stopping threads took: 0.0000281 seconds
0.288: Application time: 0.0369675 seconds
0.288: [GC (Allocation Failure)  341437K->153413K(1005056K), 0.0169709 secs]
0.305: Total time for which application threads were stopped: 0.0170923 seconds, Stopping threads took: 0.0000180 seconds
0.341: Application time: 0.0351123 seconds
0.341: [GC (Allocation Failure)  415557K->229917K(1005056K), 0.0153684 secs]
0.356: Total time for which application threads were stopped: 0.0154998 seconds, Stopping threads took: 0.0000198 seconds
0.393: Application time: 0.0372498 seconds
0.393: [GC (Allocation Failure)  492061K->302571K(1005056K), 0.0142356 secs]
0.408: Total time for which application threads were stopped: 0.0143573 seconds, Stopping threads took: 0.0000294 seconds
0.438: Application time: 0.0305803 seconds
0.438: [GC (Allocation Failure)  564715K->375184K(1005056K), 0.0152272 secs]
0.454: Total time for which application threads were stopped: 0.0153396 seconds, Stopping threads took: 0.0000206 seconds
0.487: Application time: 0.0337708 seconds
0.487: [GC (Allocation Failure)  637032K->446393K(859648K), 0.0142464 secs]
0.502: Total time for which application threads were stopped: 0.0143478 seconds, Stopping threads took: 0.0000203 seconds
0.515: Application time: 0.0136287 seconds
0.515: [GC (Allocation Failure)  563129K->489440K(932352K), 0.0108081 secs]
0.526: Total time for which application threads were stopped: 0.0109639 seconds, Stopping threads took: 0.0000375 seconds
0.545: Application time: 0.0185320 seconds
0.545: [GC (Allocation Failure)  606176K->520773K(932352K), 0.0115452 secs]
0.556: Total time for which application threads were stopped: 0.0116785 seconds, Stopping threads took: 0.0000268 seconds
0.572: Application time: 0.0155752 seconds
0.572: [GC (Allocation Failure)  637509K->554604K(932352K), 0.0163587 secs]
0.589: Total time for which application threads were stopped: 0.0164878 seconds, Stopping threads took: 0.0000230 seconds
0.619: Application time: 0.0302316 seconds
0.619: [GC (Allocation Failure)  671340K->583022K(932352K), 0.0222525 secs]
0.641: Total time for which application threads were stopped: 0.0224211 seconds, Stopping threads took: 0.0000380 seconds
0.667: Application time: 0.0258690 seconds
0.667: [GC (Allocation Failure)  699721K->612671K(932352K), 0.0161022 secs]
0.683: Total time for which application threads were stopped: 0.0162566 seconds, Stopping threads took: 0.0000319 seconds
0.701: Application time: 0.0175546 seconds
0.701: [GC (Allocation Failure)  729390K->644371K(932352K), 0.0080362 secs]
0.709: [Full GC (Ergonomics)  644371K->338491K(932352K), 0.0435653 secs]
0.753: Total time for which application threads were stopped: 0.0518384 seconds, Stopping threads took: 0.0000214 seconds
0.776: Application time: 0.0229623 seconds
0.776: [GC (Allocation Failure)  454969K->375916K(932352K), 0.0053884 secs]
0.781: Total time for which application threads were stopped: 0.0055923 seconds, Stopping threads took: 0.0000608 seconds
0.800: Application time: 0.0187336 seconds
0.800: [GC (Allocation Failure)  492286K->414216K(932352K), 0.0078718 secs]
0.808: Total time for which application threads were stopped: 0.0079946 seconds, Stopping threads took: 0.0000279 seconds
0.828: Application time: 0.0198064 seconds
0.828: [GC (Allocation Failure)  530952K->452583K(932352K), 0.0095949 secs]
0.838: Total time for which application threads were stopped: 0.0097809 seconds, Stopping threads took: 0.0000471 seconds
0.858: Application time: 0.0204690 seconds
0.858: [GC (Allocation Failure)  569319K->486930K(932352K), 0.0117822 secs]
0.870: Total time for which application threads were stopped: 0.0119662 seconds, Stopping threads took: 0.0000374 seconds
0.889: Application time: 0.0192734 seconds
0.889: [GC (Allocation Failure)  603287K->522246K(932352K), 0.0104330 secs]
0.900: Total time for which application threads were stopped: 0.0106177 seconds, Stopping threads took: 0.0000300 seconds
0.923: Application time: 0.0233277 seconds
0.923: [GC (Allocation Failure)  638982K->558575K(932352K), 0.0102558 secs]
0.934: Total time for which application threads were stopped: 0.0103861 seconds, Stopping threads took: 0.0000214 seconds
0.954: Application time: 0.0198954 seconds
0.954: [GC (Allocation Failure)  675311K->599026K(932352K), 0.0108273 secs]
0.965: Total time for which application threads were stopped: 0.0109825 seconds, Stopping threads took: 0.0000223 seconds
0.986: Application time: 0.0213374 seconds
0.986: [GC (Allocation Failure)  715762K->629449K(932352K), 0.0103810 secs]
0.996: Total time for which application threads were stopped: 0.0105217 seconds, Stopping threads took: 0.0000235 seconds
1.020: Application time: 0.0234059 seconds
1.020: [GC (Allocation Failure)  746185K->661193K(932352K), 0.0120998 secs]
1.032: [Full GC (Ergonomics)  661193K->354043K(932352K), 0.0492329 secs]
1.082: Total time for which application threads were stopped: 0.0616539 seconds, Stopping threads took: 0.0000518 seconds
1.112: Application time: 0.0307599 seconds
1.112: [GC (Allocation Failure)  470755K->395681K(932352K), 0.0047586 secs]
1.117: Total time for which application threads were stopped: 0.0049199 seconds, Stopping threads took: 0.0000277 seconds
1.122: Application time: 0.0048925 seconds

    5)查看收集器的Ergonomics(自适应)机制:

查看详情
# Ergonomics机制:自动设置堆空间各分代区域大小、收集目标等内容。
# VM参数 -Xmx1g -Xms1g -XX:+PrintAdaptiveSizePolicy -Xloggc:d:\\gc\\gc.log
OpenJDK 64-Bit Server VM (25.382-b05) for windows-amd64 JRE (1.8.0_382-b05), built on Jul 14 2023 19:32:03 by "Administrator" with MS VC++ 12.0 (VS2013)
Memory: 4k page, physical 16595576k(6834016k free), swap 19085944k(5030536k free)
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintAdaptiveSizePolicy -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.234: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44552080  promoted: 37943600  overflow: true
AdaptiveSizeStart: 0.244 collection: 1 
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 536870912 old_eden_size: 268435456 eden_limit: 268435456 cur_eden: 268435456 max_eden_size: 268435456 avg_young_live: 44552080
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.045314 major_cost: 0.000000 mutator_cost: 0.954686 throughput_goal: 0.990000 live_space: 312987520 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 268435456
AdaptiveSizeStop: collection: 1 
 262144K->80562K(1005056K), 0.0108073 secs]
0.281: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44563096  promoted: 67969344  overflow: true
AdaptiveSizeStart: 0.295 collection: 2 
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 536870912 old_eden_size: 268435456 eden_limit: 268435456 cur_eden: 268435456 max_eden_size: 268435456 avg_young_live: 44557588
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.162842 major_cost: 0.000000 mutator_cost: 0.837158 throughput_goal: 0.990000 live_space: 312993056 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 268435456
AdaptiveSizeStop: collection: 2 
 342706K->146949K(1005056K), 0.0142459 secs]
0.330: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44562704  promoted: 77478048  overflow: true
AdaptiveSizeStart: 0.345 collection: 3 
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 536870912 old_eden_size: 268435456 eden_limit: 268435456 cur_eden: 268435456 max_eden_size: 268435456 avg_young_live: 44559276
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.205801 major_cost: 0.000000 mutator_cost: 0.794199 throughput_goal: 0.990000 live_space: 312994720 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 268435456
AdaptiveSizeStop: collection: 3 
 409093K->222611K(1005056K), 0.0146753 secs]
0.378: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44564232  promoted: 78595848  overflow: true
AdaptiveSizeStart: 0.392 collection: 4 
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 536870912 old_eden_size: 268435456 eden_limit: 268435456 cur_eden: 268435456 max_eden_size: 268435456 avg_young_live: 44560516
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.228406 major_cost: 0.000000 mutator_cost: 0.771594 throughput_goal: 0.990000 live_space: 312995968 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 268435456
AdaptiveSizeStop: collection: 4 
 484755K->299366K(1005056K), 0.0141805 secs]
0.426: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44564480  promoted: 73591912  overflow: true
AdaptiveSizeStart: 0.440 collection: 5 
  avg_survived_padded_avg: 142260752.000000  avg_promoted_padded_avg: 97689952.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 6  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 536870912 old_eden_size: 268435456 eden_limit: 268435456 cur_eden: 268435456 max_eden_size: 268435456 avg_young_live: 44561308
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.244537 major_cost: 0.000000 mutator_cost: 0.755463 throughput_goal: 0.990000 live_space: 312996768 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 268435456
AdaptiveSizePolicy::survivor space sizes: collection: 5 (44564480, 44564480) -> (44564480, 44564480) 
AdaptiveSizeStop: collection: 5 
 561510K->371233K(1005056K), 0.0141221 secs]
0.477: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44550296  promoted: 73790120  overflow: true
AdaptiveSizeStart: 0.490 collection: 6 
  avg_survived_padded_avg: 141139936.000000  avg_promoted_padded_avg: 96576856.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 5  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 536870912 old_eden_size: 268435456 eden_limit: 268435456 cur_eden: 268435456 max_eden_size: 268435456 avg_young_live: 44559548
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.251404 major_cost: 0.000000 mutator_cost: 0.748596 throughput_goal: 0.990000 live_space: 312995008 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 268435456
AdaptiveSizePolicy::survivor space sizes: collection: 6 (44564480, 44564480) -> (44564480, 119013376) 
AdaptiveSizeStop: collection: 6 
 632960K->443280K(859648K), 0.0136410 secs]
0.505: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 72391944  promoted: 6189912  overflow: false
AdaptiveSizeStart: 0.514 collection: 7 
  avg_survived_padded_avg: 146075200.000000  avg_promoted_padded_avg: 106299944.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 4  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 239075328 old_eden_size: 268435456 eden_limit: 193986560 cur_eden: 119537664 max_eden_size: 193986560 avg_young_live: 48456084
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.277959 major_cost: 0.000000 mutator_cost: 0.722041 throughput_goal: 0.990000 live_space: 316891552 free_space: 536870912 old_eden_size: 268435456 desired_eden_size: 193986560
AdaptiveSizePolicy::survivor space sizes: collection: 7 (119013376, 44564480) -> (119013376, 119013376) 
AdaptiveSizeStop: collection: 7 
 559960K->476514K(932352K), 0.0085090 secs]
0.532: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 97004576  promoted: 11840224  overflow: false
AdaptiveSizeStart: 0.544 collection: 8 
  avg_survived_padded_avg: 143371840.000000  avg_promoted_padded_avg: 110053616.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 3  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 239075328 old_eden_size: 193986560 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 54281904
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.308969 major_cost: 0.000000 mutator_cost: 0.691031 throughput_goal: 0.990000 live_space: 322717376 free_space: 462422016 old_eden_size: 193986560 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 8 
 592990K->512112K(932352K), 0.0122454 secs]
0.560: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 113566768  promoted: 16035976  overflow: false
AdaptiveSizeStart: 0.573 collection: 9 
  avg_survived_padded_avg: 142359072.000000  avg_promoted_padded_avg: 110789344.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 2  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 191365120 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 60803240
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.345341 major_cost: 0.000000 mutator_cost: 0.654659 throughput_goal: 0.990000 live_space: 329238688 free_space: 387973120 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 9 
 628848K->543947K(932352K), 0.0134943 secs]
0.589: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 76792776  promoted: 63616992  overflow: false
AdaptiveSizeStart: 0.605 collection: 10 
  avg_survived_padded_avg: 143877600.000000  avg_promoted_padded_avg: 109855440.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 191365120 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 62402196
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.383562 major_cost: 0.000000 mutator_cost: 0.616438 throughput_goal: 0.990000 live_space: 330837664 free_space: 387973120 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 10 
 660353K->570160K(932352K), 0.0159447 secs]
0.620: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 41018992  promoted: 66852408  overflow: false
AdaptiveSizeStart: 0.632 collection: 11 
  avg_survived_padded_avg: 150518272.000000  avg_promoted_padded_avg: 109833688.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 191365120 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 60263876
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.403822 major_cost: 0.000000 mutator_cost: 0.596178 throughput_goal: 0.990000 live_space: 328699328 free_space: 387973120 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 11 
 686783K->600511K(932352K), 0.0126882 secs]
0.648: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 37061704  promoted: 38506616  overflow: false
AdaptiveSizeStart: 0.656 collection: 12 
  avg_survived_padded_avg: 155474976.000000  avg_promoted_padded_avg: 106503648.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 191365120 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 57943660
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.392894 major_cost: 0.000000 mutator_cost: 0.607106 throughput_goal: 0.990000 live_space: 326379104 free_space: 387973120 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 12 
 716830K->634250K(932352K), 0.0086502 secs]
0.656: [Full GC (Ergonomics) AdaptiveSizeStart: 0.701 collection: 13 
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 181293760 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 52149296
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.392894 major_cost: 0.064074 mutator_cost: 0.543032 throughput_goal: 0.990000 live_space: 320584768 free_space: 387973120 old_eden_size: 119537664 desired_eden_size: 119537664
PSAdaptiveSizePolicy::compute_old_gen_free_space: costs minor_time: 0.392894 major_cost: 0.064074 mutator_cost: 0.543032 throughput_goal: 0.990000 live_space: 664859904 free_space: 387973120 old_promo_size: 268435456 desired_promo_size: 306184192
AdaptiveSizeStop: collection: 13 
 634250K->336206K(932352K), 0.0444051 secs]
0.722: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 41951584  promoted: 0  overflow: false
AdaptiveSizeStart: 0.726 collection: 14 
  avg_survived_padded_avg: 157474496.000000  avg_promoted_padded_avg: 101385424.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 179045570 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 51129524
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.309502 major_cost: 0.064074 mutator_cost: 0.626424 throughput_goal: 0.990000 live_space: 663840128 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 14 
 452942K->377174K(932352K), 0.0042206 secs]
0.744: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 35092488  promoted: 37479288  overflow: false
AdaptiveSizeStart: 0.751 collection: 15 
  avg_survived_padded_avg: 159104928.000000  avg_promoted_padded_avg: 97312672.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 178942953 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 49525820
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.306416 major_cost: 0.064074 mutator_cost: 0.629510 throughput_goal: 0.990000 live_space: 662236416 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 15 
 493910K->407077K(932352K), 0.0075022 secs]
0.767: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 35979192  promoted: 32492792  overflow: false
AdaptiveSizeStart: 0.774 collection: 16 
  avg_survived_padded_avg: 159034720.000000  avg_promoted_padded_avg: 94263112.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 178949295 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 48171160
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.306605 major_cost: 0.064074 mutator_cost: 0.629321 throughput_goal: 0.990000 live_space: 660881792 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 16 
 523813K->439674K(932352K), 0.0069974 secs]
0.793: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 47231680  promoted: 29652504  overflow: false
AdaptiveSizeStart: 0.800 collection: 17 
  avg_survived_padded_avg: 155834368.000000  avg_promoted_padded_avg: 91658112.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 178748496 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 48077212
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.300706 major_cost: 0.064074 mutator_cost: 0.635220 throughput_goal: 0.990000 live_space: 660787840 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 17 
 556410K->479620K(932352K), 0.0076411 secs]
0.817: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 40476824  promoted: 38181032  overflow: false
AdaptiveSizeStart: 0.825 collection: 18 
  avg_survived_padded_avg: 153304384.000000  avg_promoted_padded_avg: 87478160.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 159373614 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 47317172
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.303917 major_cost: 0.064074 mutator_cost: 0.632009 throughput_goal: 0.990000 live_space: 660027776 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 18 
 596356K->510310K(932352K), 0.0078006 secs]
0.840: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 48839848  promoted: 35312184  overflow: false
AdaptiveSizeStart: 0.849 collection: 19 
  avg_survived_padded_avg: 148705104.000000  avg_promoted_padded_avg: 84087128.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 159660439 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 47469440
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.316929 major_cost: 0.064074 mutator_cost: 0.618997 throughput_goal: 0.990000 live_space: 660180032 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 19 
 627046K->552961K(932352K), 0.0085153 secs]
0.866: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 44090224  promoted: 41026912  overflow: false
AdaptiveSizeStart: 0.874 collection: 20 
  avg_survived_padded_avg: 144788464.000000  avg_promoted_padded_avg: 79881144.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 159678881 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 47131520
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.317797 major_cost: 0.064074 mutator_cost: 0.618129 throughput_goal: 0.990000 live_space: 659842112 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 20 
 669697K->588388K(932352K), 0.0082118 secs]
0.891: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 37828360  promoted: 41513288  overflow: false
AdaptiveSizeStart: 0.899 collection: 21 
  avg_survived_padded_avg: 141673536.000000  avg_promoted_padded_avg: 76085648.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 159734360 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 46201204
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.320432 major_cost: 0.064074 mutator_cost: 0.615493 throughput_goal: 0.990000 live_space: 658911808 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 21 
 705124K->622813K(932352K), 0.0083924 secs]
0.917: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 39744288  promoted: 32501456  overflow: false
AdaptiveSizeStart: 0.924 collection: 22 
  avg_survived_padded_avg: 137786272.000000  avg_promoted_padded_avg: 74123264.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 159642726 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 45555512
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.316098 major_cost: 0.064074 mutator_cost: 0.619827 throughput_goal: 0.990000 live_space: 658266112 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 22 
 739549K->656424K(932352K), 0.0075764 secs]
0.940: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 37612048  promoted: 37175600  overflow: false
AdaptiveSizeStart: 0.950 collection: 23 
  avg_survived_padded_avg: 134019656.000000  avg_promoted_padded_avg: 71322912.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 159848339 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 44761164
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.325963 major_cost: 0.064074 mutator_cost: 0.609962 throughput_goal: 0.990000 live_space: 657471744 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 23 
 773160K->690646K(932352K), 0.0090992 secs]
0.950: [Full GC (Ergonomics) AdaptiveSizeStart: 0.993 collection: 24 
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 155944439 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 40285048
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.325963 major_cost: 0.105898 mutator_cost: 0.568139 throughput_goal: 0.990000 live_space: 652995648 free_space: 425721856 old_eden_size: 119537664 desired_eden_size: 119537664
PSAdaptiveSizePolicy::compute_old_gen_free_space limits: desired_promo_size: 381264507 promo_limit: 353772352 free_in_old_gen: 353772352 max_old_gen_size: 716177408 avg_old_live: 362405056
PSAdaptiveSizePolicy::compute_old_gen_free_space: costs minor_time: 0.325963 major_cost: 0.105898 mutator_cost: 0.568139 throughput_goal: 0.990000 live_space: 671125568 free_space: 425721856 old_promo_size: 306184192 desired_promo_size: 353370112
AdaptiveSizeStop: collection: 24 
 690646K->371616K(932352K), 0.0432581 secs]
1.020: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 38311432  promoted: 8192  overflow: false
AdaptiveSizeStart: 1.024 collection: 25 
  avg_survived_padded_avg: 129885712.000000  avg_promoted_padded_avg: 75031608.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 153717394 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 40087688
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.257533 major_cost: 0.105898 mutator_cost: 0.636569 throughput_goal: 0.990000 live_space: 670928192 free_space: 472907776 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 25 
 488352K->409037K(932352K), 0.0039676 secs]
1.046: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 43755936  promoted: 34531744  overflow: false
AdaptiveSizeStart: 1.055 collection: 26 
  avg_survived_padded_avg: 124695920.000000  avg_promoted_padded_avg: 71416416.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 153947657 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 40454512
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.263586 major_cost: 0.105898 mutator_cost: 0.630516 throughput_goal: 0.990000 live_space: 671295040 free_space: 472907776 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 26 
 525773K->448076K(932352K), 0.0087297 secs]
1.073: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 40475176  promoted: 40986832  overflow: false
AdaptiveSizeStart: 1.081 collection: 27 
  avg_survived_padded_avg: 120240512.000000  avg_promoted_padded_avg: 69725008.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 119013376
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 145719332 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 40456576
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.277419 major_cost: 0.105898 mutator_cost: 0.616683 throughput_goal: 0.990000 live_space: 671297088 free_space: 472907776 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizeStop: collection: 27 
 564812K->484899K(932352K), 0.0085535 secs]
1.098: [GC (Allocation Failure) AdaptiveSizePolicy::update_averages:  survived: 38675992  promoted: 36147912  overflow: false
AdaptiveSizeStart: 1.105 collection: 28 
  avg_survived_padded_avg: 116139840.000000  avg_promoted_padded_avg: 66457408.000000  avg_pretenured_padded_avg: 0.000000  tenuring_thresh: 1  target_size: 116391936
PSAdaptiveSizePolicy::compute_eden_space_size limits: desired_eden_size: 145949501 old_eden_size: 119537664 eden_limit: 119537664 cur_eden: 119537664 max_eden_size: 119537664 avg_young_live: 40278520
PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: 0.286455 major_cost: 0.105898 mutator_cost: 0.607647 throughput_goal: 0.990000 live_space: 671119040 free_space: 472907776 old_eden_size: 119537664 desired_eden_size: 119537664
AdaptiveSizePolicy::survivor space sizes: collection: 28 (119013376, 119013376) -> (119013376, 116391936) 
AdaptiveSizeStop: collection: 28 
 601635K->518443K(934912K), 0.0076277 secs]

    6)查看熬过收集后剩余对象的年龄分布信息:

查看详情
# VM参数 -Xmx1g -Xms1g -XX:+PrintTenuringDistribution -Xloggc:d:\\gc\\gc.log
OpenJDK 64-Bit Server VM (25.382-b05) for windows-amd64 JRE (1.8.0_382-b05), built on Jul 14 2023 19:32:03 by "Administrator" with MS VC++ 12.0 (VS2013)
Memory: 4k page, physical 16595576k(6793364k free), swap 19085944k(5018192k free)
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.251: [GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
 262144K->77167K(1005056K), 0.0106601 secs]
0.297: [GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
 339311K->148698K(1005056K), 0.0168431 secs]
0.349: [GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
 410842K->220483K(1005056K), 0.0138825 secs]
0.400: [GC (Allocation Failure) 
Desired survivor size 44564480 bytes, new threshold 7 (max 15)
 482627K->295071K(1005056K), 0.0152177 secs]
0.454: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 6 (max 15)
 557215K->367871K(1005056K), 0.0137273 secs]
0.502: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 5 (max 15)
 630015K->440457K(859648K), 0.0135942 secs]
0.530: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 4 (max 15)
 557193K->478072K(932352K), 0.0083706 secs]
0.553: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 3 (max 15)
 594766K->510379K(932352K), 0.0109456 secs]
0.578: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 2 (max 15)
 626730K->538179K(932352K), 0.0128859 secs]
0.606: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 654915K->565160K(932352K), 0.0141146 secs]
0.636: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 681896K->592366K(932352K), 0.0112850 secs]
0.661: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 709059K->627098K(932352K), 0.0075703 secs]
0.683: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 743834K->661328K(932352K), 0.0078308 secs]
0.691: [Full GC (Ergonomics)  661328K->334575K(932352K), 0.0424947 secs]
0.753: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 451311K->371579K(932352K), 0.0040235 secs]
0.772: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 488315K->404949K(932352K), 0.0071697 secs]
0.795: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 521684K->442605K(932352K), 0.0083711 secs]
0.819: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 559341K->478072K(932352K), 0.0079209 secs]
0.843: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 594808K->516113K(932352K), 0.0076133 secs]
0.867: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 632831K->545989K(932352K), 0.0082046 secs]
0.891: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 662442K->582484K(932352K), 0.0075331 secs]
0.913: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 699216K->615347K(932352K), 0.0073413 secs]
0.936: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 731863K->643803K(932352K), 0.0069251 secs]
0.959: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 760539K->678938K(932352K), 0.0074526 secs]
0.966: [Full GC (Ergonomics)  678938K->340322K(932352K), 0.0384871 secs]
1.026: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 457058K->379438K(932352K), 0.0039829 secs]
1.049: [GC (Allocation Failure) 
Desired survivor size 119013376 bytes, new threshold 1 (max 15)
 496174K->420106K(932352K), 0.0084216 secs]
1.077: [GC (Allocation Failure) 
Desired survivor size 113770496 bytes, new threshold 1 (max 15)
 536842K->459474K(937472K), 0.0090345 secs]
1.106: [GC (Allocation Failure) 
Desired survivor size 109051904 bytes, new threshold 1 (max 15)
 581298K->497005K(932352K), 0.0091583 secs]

# Java技术体系图


# main方法的执行过程

package com.howl.jvm;

/**
 * 0.类加载过程,首先获取类的二进制流,然后将类的方法代码,变量名,方法名,返回值等存储到方法区(元空间)。执行类里* 的static{}静态代码块代码,生成类的class对象,该对象会被存储到堆内存中。同时在常量池(元空间)中增加类的符号引用信* 息,代表类初始化完成。
 * 1.创建main线程,把main方法作为栈帧压入main线程,开始执行main方法
 * 2.(+/-表示可选分支)如果此时线程调度,会把当前线程所执行的字节码的行号指示器,也就是当前方法具体执行到了什么位置信息存储到程序* 计数器上,后续用这个计数器恢复线程。
 * 3.识别到了new关键字,创建A对象,执行步骤0,然后执行步骤4
 * 4.创建a对象,将对象加载到堆区(对象头以及实例属性)
 * 5.执行对象的test方法(将test方法作为栈帧压入当前线程栈区执行,执行完成后回到main栈帧)
 * 6.操作对象的实例属性(将该实例属性复制到当前线程的工作内存,也就是栈区),修改后再同步到主内存中(堆中对象的属性)。
 */
public class Test {
    public static void main(String[] args) {
        A a = new A();
		a.test();
		a.i=2;
    }
}

class A{
    private int i;
    public void test(){
        System.out.println("test");
    }

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }
}

# 调优案例分析与实战

# 由Windows虚拟内存导致的长时间停顿

这个案例建议参考原论坛发文结合周老师的案例讲解一下看,原贴地址:FULL GC有可能导致JVM暂停1分钟以上吗? (opens new window)

# Safepoint导致长时间STW

这个案例来自小米,参见HBase实战:记一次Safepoint导致长时间STW的踩坑之旅 (opens new window)

# B站宕机事故复盘:2021.07.13 我们是这样崩的

这个宕机其实和Java和JVM都没什么关系了,不过B站这个复盘报告写的还可以,值得参考B站宕机事故复盘:2021.07.13 我们是这样崩的 (opens new window)

# jvm脑图


# QPS,TPS,PV分别是指什么?

# QPS

QPS 是英文 Queries Per Seconds 的缩写,翻译成中文即每秒请求数。此外,还有一个峰值每秒请求数,可以根据PV得出。 参考QPS是什么? (opens new window)

# TPS

参考QPS是什么? (opens new window)

# PV

pv 是指页面被浏览的次数,如你打开了一个网页,那么这个网站的pv就算加了一次。