JVM 原理与实战【java进阶笔记十三】

目录

一、 JVM概述

二、 JMM 虚拟机内存模型

1. 程序计数器 (PC 寄存器)

2. 虚拟机栈 & 本地方法栈

3. 堆

4. 方法区

5. 永久代

6. 元空间

7. 直接内存

三、JVM 垃圾回收算法

1. 什么是垃圾回收?

2. 可触及性

3. 四种引用级别

4. 面试题:

5.主要垃圾回收算法

四、JVM 垃圾收集器

1.串行回收器 - Serial

2.并行回收器 - ParNew & ParallelGC & ParallelOldGC

3.并行回收器 - CMS (Concurrent Mark Sweep)

4.G1 (Garbage First Garbage Collector)

五、JVM 常用参数

六、JVM 监控优化


一、 JVM概述

  • Java 程序的跨平台特性

同一个 Java 程序,被编译为一组 Java 字节码 的集合之后,就可以通过 Java虚拟机 运行于不同的操作系统上,它以 Java 虚拟机为中介,实现跨平台的特性。

  • JVM 类加载流程和内存结构总览

① 类主动加载有⼏种⽅式? 

1> Student student = new Student() 直接 new 一个对象;

2> 利⽤反射、clone;

3> 初始化⼦类的时候,⽗类会被初始化;

4> 调⽤⼀个类的静态⽅法

② 类加载的五个步骤:

  • 第⼀步:加载 Classloader

    通过类的全路径名,获取类的⼆进制数据流。

    获得了类的信息,解析类的数据流,转化为⽅法区内部的数据结构

    创建 java.lang.Class 类的实例。

  • 第⼆步:验证

    判断我们的字节码是不是合法,是不是规范。

    验证的步骤:格式的验证、语义的验证、字节码的验证、符号引⽤的验证(什么是符号引⽤?什么是直接引⽤?)。

  • 第三步:准备

    分配相应的内存空间。

  • 第四步:解析

    将符号引⽤转换为直接引⽤。

  • 第五步:初始化

    类已经被加载到系统中了,开始执⾏字节码。

③ 什么是符号引⽤?什么是直接引⽤?

  • 符号引⽤

    以⼀组 符号来描述 引用的⽬标直接的关系。

  • 直接引⽤

    通过 Class ⽂件加载到内存之后,通过对符号引⽤的转换,就有了对应的直接引⽤。

  • 为什么有符号引⽤?

    java 类被编译成 Class⽂件,并不知道具体的引⽤地址,所以就以符号引⽤代替。

    在解析阶段,把符号引⽤转换为了真正的地址,也就是转换为直接引⽤。

二、 JMM 虚拟机内存模型

1. 程序计数器 (PC 寄存器)

1> 是 当前线程 所执行的 字节码的行号指示器,指向虚拟机字节码指令的位置;

2> 被分配了一块 较小的内存空间

3> 针对于 非Native方法:是当前线程执行的字节码的行号指示器;

​ 针对于 Native方法:则为undefined;

4> 每个线程都有自己独立的程序计数器,所以该内存是 线程私有 的;

5> 这块区域是 唯一 一个在虚拟机中 没有规定任何OutOfMemoryError 情况的区域

2. 虚拟机栈 & 本地方法栈

1> 虚拟机栈为执行 Java 方法服务,是描述方法执行的 内存模型

2> 本地方法栈贼是为本地方法服务的;

3> 栈是 线程私有 的内存空间;

4> 在栈中保存的主要内容为 栈帧。它的 数据结构 就是 先进后出。每当函数被调用,该函数就会被 入栈,每当函数执行完毕,就会执行 出栈 操作。而当前 栈顶,即为正在执行的函数;

栈帧与⽅法调⽤相对应:

⼊栈 ——> ⽅法调⽤

出栈 ——> 结果返回

栈顶 ——> 当前正在执⾏的⽅法

5> 每个方法在执行的同时都会创建一个 栈帧 用于存储 局部变量表操作数栈帧数据区动态链接方法出口 等信息。

实战:

(1) 栈的⼤⼩决定⽅法调⽤深度:(通过 count 表示调用深度)

public class StackOverflowTest {
    // 为了记录方法调用层级数的
    private static int count=0;

    public static void main(String[] args) {
        try {
            while (true) {
                count();
            }
        } catch (Throwable e) {
            System.out.println("count = " + count);
            throw e;
        }
    }

    private static void count() {
        count++;
        count();
    }
}

① 设置最⼤栈内存为 -Xss160K

 ② 设置最⼤栈内存为 -Xss256K

(2) 栈帧中的 局部变量表 演示:说明 方法入参、局部变量 是存在栈中的(栈空间越大,存的入参和变量越多,层次也能越深)

public class StackOverflow2Test {
    private static int count=0;

    public static void main(String[] args) {
        try {
            while (true) {
                count(1L, 2L, 3L, 4L, 5L);
            }
        } catch (Throwable e) {
            System.out.println("count = " + count);
            throw e;
        }
    }

    private static void count(long arg1, long arg2, long arg3, long arg4, long arg5) {
        long num1 = 1;
        long num2 = 2;
        long num3 = 3;
        long num4 = 4;
        long num5 = 5;
        long num6 = 6;
        long num7 = 7;
        long num8 = 8;
        count++;
        count(arg1, arg2, arg3, arg4, arg5);
    }
}

 ① 设置最⼤栈内存为 -Xss160K

 ② 设置最⼤栈内存为 -Xss256K

3. 堆

1> 运行时数据区,几乎所有的对象都保存在 java 堆中

2> Java 堆是 完全自动化管理 的,通过垃圾回收机制,垃圾对象会被自动清理,不需要显式地释放;

3> 堆是垃圾收集器 进行GC的最重要内存区域

4> Java 堆可以分为:新生代(Eden区、S0区、S1区)和 老年代

5> 在绝大多数情况下,对象 首先分配在 eden 区,在 一次新生代 GC回收后,如果对象还存活,则会进入S0或S1。之后每经历过一次新生代回收,对象如果存活,它的年龄就会加一。当对象的 年龄达到一定条件后,就会被认为是老年代对象,从而进入老年代。

4. 方法区

1> 逻辑上的东西,是 JVM的规范,是所有虚拟机必须遵守的;

2> 是 JVM 所有 线程共享 的、用于 存储类信息,例如:类的字段、方法数据、常量池等;

3> 方法区的大小决定了系统可以保存多少个类;

4> JDK8之前——永久代

​     JDK8及之后——元空间

5. 永久代

指内存的 永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候放入永久区域,它和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出OOM异常。

如果系统使用了一些 动态代理,那么有可能会在运行时生成大量的类,从而造成内存溢出。所以,设置合适的永久代大小,对于系统的 稳定性 是至关重要。

-XX:PermSize

​         设置初始永久代大小。例如:-XX:PermSize=5MB

-XX:MaxPermSize

​         设置最大永久代大小,默认情况下为64MB。例如:-XX:MaxPermSize=5MB

6. 元空间

1> 在 Java8 中,永久代已经被移除,被一个称为 “元数据区”(元空间)的区域所取代;

2> 元空间的本质和永久代类似,元空间与永久代之间 最大的区别 在于:元空间并不在虚拟机中,而是使用堆外的直接内存

3> 因此与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存

-XX:MaxMetaspaceSize

​         设置最大元数据空间。例如:-XX:MaxMetaspaceSize=20MB

为什么使用元空间替换永久代?

1> 避免OOM的异常。因为元空间使⽤的是堆外内存,默认情况下,虚拟机会耗尽所有的可⽤系统内存;

2> JRockit 没有永久代,也不需要运维⼈员设置⼤⼩,运⾏的还不错,有性能的损耗,但是不⼤。性能的部分损耗来换取了更⼤的安全保障。Oracle 可能会将 HotSpot 和 JRockit 合⼆为⼀。

7. 直接内存

跟 NIO 息息相关的;

堆外内存,直接向系统申请内存空间;

与 Java 堆相⽐,读写访问有⼀定的优势。但是在内存空间申请上,速度偏低;

应⽤场景:内存空间申请次数少,并且访问较为频繁的情况。

三、JVM 垃圾回收算法

1. 什么是垃圾回收?

GC:垃圾回收,即:Garbage Collection。

垃圾:特指存在于 内存 中的、不会再被使用的对象。

回收:清除内存中的“垃圾”对象。


2. 可触及性

① 什么是可触及性?

就是 GC 时,是根据它来确定对象是否可被回收

也就是说,从根节点开始是否可以访问到某个对象,就说明这个对象是否被使用。

② 可触及性分为3种状态:

可触及:从根节点开始,可以到达某个对象;

可复活:对象引用被释放,但是可能在 finalize() 函数中被初始化复活;

不可触及:由于 finalize() 只会执行一次,所以错过这一次复活机会的对象,则为不可触及状态。

例:

public class DieAliveObject {
    private static DieAliveObject dieAliveObject;

    public static void main(String[] args) {
        dieAliveObject = new DieAliveObject(); // 可触及状态

        for (int i = 0; i <= 1; i++) { // 两次循环
            System.out.println(String.format("----------GC nums=%d----------", i));
            dieAliveObject = null; // 将dieAliveObject对象置为"垃圾对象"
            System.gc(); // 通知JVM可以执行GC了
            try {
                Thread.sleep(100); // 等待GC执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (dieAliveObject == null) {
                System.out.println("dieAliveObject is null");
            } else {
                System.out.println("dieAliveObject is not null");
            }
        }
    }

    /**
     * finalize只会被调用一次,给对象唯一一次重生的机会
     */
    @Override
    protected void finalize() {
        System.out.println("finalize is called!");
        dieAliveObject = this; // 使对象复生,添加引用
    }
}


3. 四种引用级别

① 强引用:

就是一般程序中的引用,例如 Student student = new Student();

② 软引用(java.lang.ref.SoftReferenct):

堆空间不足时,才会被回收 。因此,软引用对象不会引起内存溢出。

③ 弱引用(java.lang.ref.WeakReferenct):

当 GC 的时候,只要发现存在弱引用,无论系统堆空间是否不足,均会将其回收。

④ 虚引用:

如果对象持有虚引用,其实与没有引用是一样的。虚引用必须和引用队列在一起使用,它的作用是用于跟踪 GC 回收过程,所以可以将一些资源释放操作放置在虚引用中执行和记录。

例:

软引用

内存空间充足的时候执行第一次 GC ,teacher对象不会被回收;创建一个大对象造成空间紧张后,执行第二次 GC ,teacher软引用对象被回收。

public class SoftReferenceDemo {
    public static void main(String[] args) throws Throwable{
        /** 查看空余内存 */
        System.out.println("---------Free " + Runtime.getRuntime().freeMemory() / 1000000 + "M----------");

        /** 创建Teacher对象的软引用 */
        Teacher teacher = new Teacher("aaa", 15);
        SoftReference softReference = new SoftReference(teacher);
        System.out.println("softReference=" + softReference.get());

        /** 使得teacher失去引用,可被GC回收 */
        teacher = null;

        /** 执行第一次GC后,软引用并未被回收 */
        System.gc();
        System.out.println("---------First GC----------");
        System.out.println("softReference=" + softReference.get());

        /** 可以通过对数组大小数值调整,来造成内存资源紧张 */
        byte[] bytes = new byte[7*971*1024]; 
        System.out.println("---------Assign Big Object----------");

        /** 执行第二次GC,由于堆空间不足,所以软引用已经被回收 */
        System.gc();
        System.out.println("---------Second GC----------");
        Thread.sleep(1000); // 睡眠1秒钟,保证GC已经执行完毕
        System.out.println("softReference=" + softReference.get());
    }
}

弱引用

创建 teacher 弱引用对象,执行 GC前为被回收 ,执行 GC 后 teacher 弱引用对象被回收。

public class WeakReferenceDemo {
    public static void main(String[] args) throws Throwable{
        /** 创建Teacher对象的弱引用 */
        Teacher teacher = new Teacher("aaa", 15);
        WeakReference<Object> weakReference = new WeakReference<>(teacher); /** 创建弱引用对象 */

        /** 使得teacher失去引用,可被GC回收 */
        teacher = null;

        /** 执行GC前,查看弱引用并未被回收 */
        System.out.println("---------Before GC----------");
        System.out.println("weakReference=" + weakReference.get());

        /** 执行GC,所以弱引用已经被回收 */
        System.gc();
        System.out.println("---------After GC----------");
        Thread.sleep(1000); // 睡眠1秒钟,保证GC已经执行完毕
        System.out.println("weakReference=" + weakReference.get());
    }
}

虚引用

虚引用必须和引用队列一起使用。因为垃圾回收时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入到引用队列。通过判断是否能从虚引用队列中获取到对象,从而判断是否回收成功。

public class PhantomReferenceDemo {
    private static PhantomReferenceDemo obj;

    public static void main(String[] args) {
        /** 创建引用队列 */
        ReferenceQueue<PhantomReferenceDemo> phantomRefQueue = new ReferenceQueue<>();

        /** 创建虚引用 */
        obj = new PhantomReferenceDemo();
        PhantomReference<PhantomReferenceDemo> phantomReference = new PhantomReference<>(obj, phantomRefQueue);
        System.out.println("phantomReference = " + phantomReference.get()); // 总会返回null

        /** 创建后台线程 */
        Thread thread = new CheckRefQueueThread(phantomRefQueue);
        thread.setDaemon(true);
        thread.start();

        /** 执行两次GC,一次被finalize复活,一次真正被回收 */
        for (int i = 1; i <=2 ; i++) {
            gc(i);
        }
    }

    private static void gc(int nums) {
        obj = null;
        System.gc();
        System.out.println("---------第" + nums + "次GC----------");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (obj == null) {
            System.out.println("obj is null");
        } else {
            System.out.println("obj is not null");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize() is called!");
        obj = this; // 复活对象
    }
}

/**
 * 从引用队列中获得被回收的对象
 */
class CheckRefQueueThread extends Thread {
    private ReferenceQueue<PhantomReferenceDemo> phantomRefQueue;

    public CheckRefQueueThread(ReferenceQueue<PhantomReferenceDemo> phantomRefQueue ) {
        this.phantomRefQueue = phantomRefQueue;
    }

    @Override
    public void run() {
        while (true) {
            if (phantomRefQueue != null) {
                // 将虚引用对象置空,满足GC
                PhantomReference<PhantomReferenceDemo> phantomReference = null;
                try {
                    /**
                     * 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入到引用队列
                     * ,以通知应用程序对象的回收情况
                     * 
                     */
                    // 从虚引用队列中获取虚引用对象
                    phantomReference = (PhantomReference<PhantomReferenceDemo>) phantomRefQueue.remove();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
                // 能从队列中获取到对象说明已经被回收
                if (phantomReference != null) {
                    System.out.println("Object is delete by GC");
                }
            }
        }
    }
}


4. 面试题:

1、什么是槽位复用?

本地变量表中的,变量的槽位被占用。例如下面的方法 Gc4(),变量 a 的槽位序号为1,变量 b 的槽位也为1,所以 a 变量会被回收。

对象空间分配:① 栈上分配;不满足栈上分配条件的话 ② TLAB 分配;不满足 TLAB 分配条件的话 ③ (堆上分配)新生代 -> 老年代。引出以下问题:

2、什么是栈上分配?如何可以触发?

  • 是 JVM 提供的⼀种优化技术。

  • 对线程【私有对象】,可以尝试将他们打散分配到栈上,⽽不是分配在堆上。

  • 好处:分配在栈上,调⽤结束后会⾃⾏的销毁,⽽不⽤GC去介⼊。

  • 他的空间⽐较⼩。⼤的对象就不适合在栈上分配了。

  • 开启条件,需要同时满⾜以下三点:

    开启 逃逸分析

    开启 标量替换

    server 模式下

  • 逃逸分析

    就是判断对象是不是线程私有。

  • 标量替换

    什么叫做标量?

    ​      不可被进⼀步分解的量。int,long,byte

    什么叫聚合量?

    ​      可被进⼀步分解的量。例如我们创建的对象。

    把聚合量分解为标量。把对应的成员变量存储在栈帧或寄存器上。

例:

/**
 * 栈上分配(默认开启)
 * 【开启栈上分配】-Xmx50m -Xms50m -XX:+PrintFlagsFinal -XX:+PrintGCDetails -XX:-UseTLAB
 * 【关闭栈上分配】-Xmx50m -Xms50m -XX:+PrintFlagsFinal -XX:+PrintGCDetails -XX:-UseTLAB -XX:-DoEscapeAnalysis
 *  注意:-XX:+PrintFlagsFinal只是为了查看参数设置情况,可以去掉。
 *  -XX:-DoEscapeAnalysis (关闭逃逸分析,条件不足,相当于关闭栈上分配)
 **/
public class AssignOnStack {
    public static void main(String[] args) {
        sizeOfStudent();
        StopWatch stopWatch = StopWatch.createStarted();
        // 制造将近7.5个G左右的对象
        for (int i=0; i< 100000000; i++) {
            initStudent();
        }
        stopWatch.stop();
        System.out.println("========执行一共耗时:" + stopWatch.getTime(TimeUnit.MILLISECONDS) + "毫秒");
    }

    /**
     * student所占用空间为72bytes
     */
    public static void sizeOfStudent() {
        Student student = new Student();
        student.setName("muse");
        System.out.println("========student大小为:" + ObjectSizeCalculator.getObjectSize(student));
        System.out.println("========student大小为:" + RamUsageEstimator.humanSizeOf(student));
    }

    public static void initStudent() {
        Student student = new Student();
        student.setName("muse");
    }
}

默认开启栈上分配:

 关闭栈上分配,耗时明显不同:

3、什么是TLAB?好处是什么?

TLAB (Thread Local Allocation Buffer,线程本地分配缓冲区)是 Java 中内存分配的一个概念,它是在 Java 堆中划分出来的针对每个线程的内存区域,专门在该区域为该线程创建的对象分配内存。它的主要目的是在多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度

参考:什么是 Java 中的TLAB ?_hfer的博客-CSDN博客_tlab


5.主要垃圾回收算法

引用计数法、标记清除法、复制算法、标记压缩算法、分代算法、分区算法

  • 引 用 计 数 法(Reference Counting)

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。当对象A的引用计数器的值为0,则对象A不可用。

但引用计数器有两个严重问题:

(1)无法处理 循环引用 的情况。两个对象相互引用的话,计数总不为0,无法回收。

(2)引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加减法操作,对 系统性能 会有一定的影响。

* 因此:JVM并未选择引用计数法作为垃圾回收算法。

  • 标 记 清 除 法 (Mark - Sweep)

标记清除算法是 现代垃圾回收算法的思想基础

分为两个阶段:① 标记阶段② 清除阶段

标记清除算法产生 最大的问题 就是清除之后的 空间碎片

第一阶段标记:从根节点向下寻找标记有用对象;第二阶段清除:清除标记出来的垃圾对象(上图黑色部分)。

问题:从图可以看到,垃圾回收后,垃圾对象原来的空间成为了空间碎片。

  • 复 制 算 法

将原有内存空间分为两块。每次只使用其中一块内存,例如:A内存,GC 时将存活的对象复制到B内存中。然后清除掉A内存所有对象。开始使用B内存。

复制算法没有内存碎片,并且如果垃圾对象很多,那么这种算法效率很高。但是它的 缺点是系统内存只能使用1/2

复制算法在 JVM 中的使用:

因为新生代大多对象都是“朝不保夕”,所以在新生代串行GC中,使用了复制算法。(新生代中 Eden:From:to = 8:1:1,所以在 JVM中浪费的空间就相当于1/10,减少了空间的浪费)

*对象在什么情况下会进入老年代?

  1. BigObject

  2. OldObject

  3. Survivor to 空间不足

  • 标 记 压 缩 法 (Mark - Compact)

标记压缩算法是一种 老年代的回收算法

分为两个阶段:① 标记阶段②压缩阶段。它首先标记存活的对象,然后将所有存活的对象 压缩到内存的一端,然后在清理所有存活对象之外的空间。

该算法 不会产生内存碎片,并且也 不用将内存一分为二。因此其 性价比较高

  • 分 代 算 法

将堆空间划分为 新生代老年代,根据它们直接的不同特点,执行不同的回收算法,提升回收效率。

  • 分 区 算 法

将堆空间划分成连续的不同 小区间,每个区间独立使用、回收。由于当堆空间大时,一次GC的时间会非常耗时,那么可以控制每次回收多少个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿

四、JVM 垃圾收集器

1.串行回收器 - Serial

串行回收器的 特点: (STW :stop the world)

1>只使用 单线程 进行GC

2>独占式 的GC

串行收集器是 JVM Client 模式默认 的垃圾收集器

JVM 参数 作用
-XX:SurvivorRatio 设置eden区与survivor区比例
-XX:PretenureSizeThreshold 设置大对象直接进入老年代的阈值
-XX:MaxTenuringThreshold 设置对象进入老年代的年龄阈值

2.并行回收器 - ParNew & ParallelGC & ParallelOldGC

将串行回收器 多线程化

与串行回收器有 相同的回收策略、算法、参数

启动指定收集器:

JVM 参数 新生代 老年代
-XX:+UseSerialGC 串行回收器 串行回收器
-XX:+UseParNewGC ParNew 串行回收器
-XX:+UseConcMarkSweepGC ParNew CMS
-XX:+UseParallelGC ParallelGC 串行回收器
-XX:+UseParallelOldGC ParallelGC ParallelOldGC

3.并行回收器 - CMS (Concurrent Mark Sweep)

4.G1 (Garbage First Garbage Collector)

(jdk1.7 开始,目的是取代 CMS,特点是:分区

G1 全称 Garbage First Garbage Collector。优先回收垃圾比例最高的区域

G1收集器将堆 划分为多个区域,每次收集部分区域来 减少GC产生的停顿时间

G1 的 GC 过程:

 

  • 第二阶段:并发标记周期

  • 第三阶段:混合收集

    JVM参数 作用
    -XX:+UseG1GC 打开G1收集器开关
    -XX:MaxGCPauseMillis 指定目标最大停顿时间
    -XX:PartallelGCThreads 设置并发线程数量
    -XX:InitiatingHeapOccupancyPercent 指定堆的使用率,触发并发标记周期(默认45)

五、JVM 常用参数

执行语法:

Java [-options] [package+className] [arg1,arg2,…,argN]

options:

​ -Xms128m                         设置初始化堆内存为128M

​ -Xmx512m                         设置最大堆内存为512M

​ -Xmn160m                         设置新生代大小为-Xmn160M(堆空间1/4~1/3)

​ -Xss128m                           设置最大栈内存为128M

​ -XX:SurvivorRatio               设置新生代eden区与from/to空间的比例关系

​ -XX:PermSize=64M            设置初始永久区64M

​ -XX:MaxPermSize=128M    设置最大永久区128M

​ -XX:MaxMetaspaceSize     设置元数据区大小(JDK1.8 取代永久区)

​ -XX:+DoEscapeAnalysis     启用逃逸分析(Server模式)

​ -XX:+EliminateAllocations   开启标量替换(默认开启)

​ -XX:+TraceClassLoading     跟踪类的加载

​ -XX:+TraceClassUnloading 跟踪类的卸载

​ -Xloggc:gc.log                      将gc日志信息打印到gc.log文件中

​ -XX:+PrintGC                       打印GC日志

​ -XX:+PrintGCDetails            打印GC详细日志

​ -XX:+PrintGCTimeStamps   输出GC发生的时间

​ -XX:+PrintGCApplicationStoppedTime                GC产生停顿的时间

​ -XX:+PrintGCApplicationConcurrentTime            应用执行的时间

​ -XX:+PrintHeapAtGC                                           在GC发生前后,打印堆栈日志

​ -XX:+PrintReferenceGC                                       打印对象引用信息

​ -XX:+PrintVMOptions                                           打印虚拟机参数

​ -XX:+PrintCommandLineFlags                            打印虚拟机显式和隐式参数

​ -XX:+PrintFlagsFinal                                            打印所有系统参数

​ -XX:+PrintTLAB                                                    打印TLAB相关分配信息

​ -XX:+UseTLAB                                                     打开TLAB

​ -XX:TLABSize                                                      设置TLAB大小

​ -XX:+ResizeTLAB                                                自动调整TLAB大小

​ -XX:+DisableExplicitGC                                       禁用显式GC (System.gc())

​ -XX:+ExplicitGCInvokesConcurrent                     使用并发方式处理显式GC

六、JVM 监控优化

  • Linux —— top 命令

能够实时显示系统中各个进程的资源占用情况。

分为两部分:系统统计信息 & 进程信息。

 

系统统计信息:

Line1:任务队列信息,从左到右依次表示:系统当前时间、系统运行时间、当前登录用户数。Load average表示系统的平均负载,即任务队列的平均长度——1分钟、5分钟、15分钟到现在的平均值。

Line2:进程统计信息,分别是:正在运行进程数、睡眠进程数、停止的进程数、僵尸进程数。

Line3:CPU统计信息,us表示用户空间CPU占用率、sy表示内核空间CPU占用率、ni表示用户进程空间改变过优先级的进程CPU占用率。id表示空闲CPU占用率、wa表示待输入输出的CPU时间百分比、hi表示硬件中断请求、si表示软件中断请求。

Line4:内存统计信息,从左到右依次表示:物理内存总量、已使用的物理内存、空闲物理内存、内核缓冲使用量。

Line5:从左到右表示:交换区总量、已使用交换区大小、空闲交换区大小、缓冲交换区大小。

进程信息:

PID:进程id

USER:进程所有者

PR:优先级

NI:nice值,负值->高优先级,正值->低优先级

VIRT:进程使用虚拟内存总量 VIRT=SWAP+RES

RES:进程使用并未被换出的内存。CODE+DATA

SHR:共享内存大小

S:进程状态。 D=不可中断的睡眠状态 R=运行

S=睡眠 T=跟踪/停止 Z=僵尸进程

%CPU:上次更新到现在的CPU时间占用百分比

%MEM:进程使用的物理内存百分比

TIME+:进程使用的CPU时间总计,单位 1/100秒

COMMAND:命令行

  • Linux —— vmstat 命令

性能监测工具,显示单位均为kb。它可以统计CPU、内存使用情况、swap使用情况等信息,也可 以指定采样周期和采用次数。

例如:每秒采样一次,共计3次。 vmstat 1 3

procs列:r表示等待运行的进程数。b表示处于非中断睡眠状态的进程数。

memory列:swpd表示虚拟内存使用情况。free表示空闲内存量。buff表示被用来作为缓存的内存。

swap列:si表示从磁盘交换到内存的交换页数量。so表示从内存交换到磁盘的交换页数量。

io列:bi表示发送到块设备的块数,单位:块/秒。bo表示从块设备接收到的块数。

system列:in表示每秒的中断数,包括时钟中断。cs表示每秒的上下文切换次数。

cpu列:us表示用户cpu使用时间。sy表示内核cpu系统使用时间。id表示空闲时间。 wa表示等待io时间。

  • Linux —— iostat 命令

可以提供详尽的I/O信息。

如果只看磁盘信息,可以使用-d参数。即:Iostat –d 1 1 (每1秒采集一次持续1次)

 

tps列 表示该设备每秒的传输次数。

Blk_read/s列 表示每秒读取块数。

Blk_wrtn/s列 表示每秒写入块数。

Blk_read列 表示读取块数总量。

Blk_wrtn列 表示写入块数总量。

  • JDK 工具 —— jps

用于列出 Java 的进程。

执行语法: 
    jps [-options]

jps 列出java进程id和类名

​         例:91275 FireIOTest

jps –q 仅列出java进程id

​         例:91275

jps –m 输出java进程的入参

​         例:91730 FireIOTest a b

jps –l 输出主函数的完整路径

​         例:91730 day1.FireIOTest

jps –v 显示传递给JVM的参数

​         例:91730 FireIOTest -Xmx512m -XX:+PrintGC -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51673:/Applications/IntelliJIDEA.app/Contents/bin - Dfile.encoding=UTF-8

  • JDK 工具 —— jstat

用于查看堆中的运行信息。

执行语法:jstat –help jstat -options 
    jstat <-option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

jstat -class -t 73608 1000 5

​         查看进程73608的ClassLoader相关信息,每1000毫秒打印1次,一共打印5次,并输出程序启动到此刻的Timestamp数。

jstat -compiler -t 73608           查看指定进程的编译信息。

jstat -gc 73608                         查看指定进程的堆信息。

jstat -gccapacity 73608           查看指定进程中每个代的容量与使用情况

jstat -gccause 73608               显示最近一次gc信息

jstat -gcmetacapacity 73608   查看指定进程的元空间使用信息

jstat -gcnew 73608                   查看指定进程的新生代使用信息

jstat -gcnewcapacity 73608     查看指定进程的新生代各区大小信息

jstat -gcold 73608                     查看指定进程的老年代使用信息

jstat -gcoldcapacity 73608       查看指定进程的老年代各区大小信息

jstat -gcutil 73608                     查看指定进程的GC回收信息

jstat -printcompilation 73608  查看指定进程的JIT编译方法统计信息

  • JDK 工具 —— jinfo

用于查看运行中java进程的 虚拟机参数

执行语法:
    jinfo [option] <pid>

jinfo -flag MaxTenuringThreshold 73608

​         查看进程73608的虚拟机参数MaxTenuringThreshold 的值

jinfo -flag +PrintGCDetails 73608

​         动态添加进程73608的虚拟机参数+PrintGCDetails,开启GC日志打印。

jinfo -flag -PrintGCDetails 73608

​         动态添加进程73608的虚拟机参数+PrintGCDetails,关闭GC日志打印。

  • JDK 工具 —— jmap

命令用于生成指定java进程的dump文件;可以查看堆内对象实例的统计信息,查看ClassLoader信息和finalizer队列信息。

执行语法:
    jmap [option] <pid>

jmap -histo 73608 > /Users/aaa/a.txt

​         输出进程73608的实例个数与合计到文件a.txt中

jmap -dump:format=b,file=/Users/aaa/b.hprof 73608

​         输出进程73608的堆快照,可使用jhat、visual VM等进行分析

  • JDK 工具 —— jhat

命令用于分析jmap生成的堆快照。

执行语法:
    jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [- version] [-h|-help] <file>

jhat b.hprof

​         分析jmap生成的堆快照b.hprof,通过 http://127.0.0.1:7000 这个地址查看。 OQL(Object Query Language)

  • JDK 工具 —— jstack

命令用于导出指定java进程的堆栈信息。

执行语法:
    jstack [-l] <pid>

jstack -l 73608 > /Users/muse/d.txt 输出进程73608的实例个数与合计到文件a.txt中

查看文件:

  • JDK 工具 —— cmd

命令用于导出指定java进程的堆栈信息,查看进程,GC等。

执行语法:
    jcmd <pid | main class> <command ...|PerfCounter.print|-f file>

jcmd -l                         列出java进程列表

jcmd 26586 help         输出进程java进程为26586所支持的jcmd指令

jcmd 26586 VM.uptime            查看java进程启动时间

jcmd 26586 Thread.print         打印线程栈信息

jcmd 26586 GC.class_histogram         查看系统中类的统计信息

jcmd 26586 GC.heap_dump /Users/muse/a.txt         导出堆信息

jcmd 26586 VM.system_properties         获得系统的Properties内容

jcmd 26586 VM.flags                         获得启动参数

jcmd 26586 PerfCounter.print           获得性能统计相关数据

 *此笔记是对 爪哇-缪斯 老师 JVM 课程笔记的整理。

猜你喜欢

转载自blog.csdn.net/qq_42183414/article/details/125176957