后浪,谈谈你对jvm性能调优的理解

简介: 后浪,谈谈你对jvm性能调优的理解

image.png在我们日常的研发工作中, 经常会遇到系统的性能问题,这时我们必须进行系统的性能调优。系统调优分好多种,比如架构和代码优化、jvm调优、操作系统调优、数据库调优、tomcat调优、网络调优等。架构和代码优化是效率最高的调优手段,但是并不能解决所有的性能问题。今天我们要回顾的是一个老生常谈的话题,jvm调优。


本文主要包括以下内容

  • Java内存模型回顾
  • 什么时候需要JVM调优
  • 常见的OOM异常及案例
  • JVM自带监控工具
  • JVM常用调优参数
  • JVM第三方监控工具
  • 调优案例


Java内存模型回顾


首先,我们以HotSpot回顾一下JVM的内存模型,见下图:

HotSpot内存模型分为3个部分:

 

类加载器

 

类加载器用于加载java编译后的.class文件,提取其中的类信息以某种数据结构存放在方法区。

 

运行时数据区

 

线程栈和本地方法栈用于存放线程运行时方法调用等相关信息,程序计数器记录字节码指令在主内存中的地址,这3个模块都是线程私有的。

堆中存放程序运行时创建的对象。

对于jvm规范中的方法区,java8以前,HotSpot对方法区的实现是在永久代。从java7开始,HotSpot开始移除永久代,符号引用迁移到native heap,字面量和类静态变量移动到java堆。Java8中HotSpot彻底废弃了永久代,用元空间来取代永久代实现jvm规范中的方法区。元空间用来专门存储类的元数据,并且内存分配在本地内存,不占用jvm内存。

 

堆内存的分布如下:

微信图片_20221212103633.png

G1圾收集器的堆空间分配策略如下:

微信图片_20221212103701.png

后来出现的ZGC内存分配更加动态和灵活。本文以Java8为例,不讨论G1ZGC

顺便回顾一下常用的垃圾收集算法:

a. 清除算法:会造成内存碎片,内存分配效率低

微信图片_20221212103721.png

b. 压缩算法:性能开销大

 微信图片_20221212103742.png

c. 复制算法:堆使用效率较低

微信图片_20221212103803.png


常用垃圾收集器:

新生代:SerialParallel Scavenge(更加注重吞吐量,不能和CMS一起使用) Parallel New,都采用标记-复制算法

老年代:Serial Old(标记-压缩算法) Parallel Old(标记-压缩算法),以及 CMS(标记-清除算法,支持并发)java9中被G1取代


执行引擎

 

HotSpot解释执行器对加载的字节码会逐条解释成机器码进行执行,对于反复执行的热点代码,JIT Compiler会把字节码编译成机器代码后再执行。

垃圾收集器则是对死亡对象占用的堆内存空间进行回收。

 

在上面的JVM内存模型架构图中,紫色的3个区域是我们调优时的关注点。Heap是存放对象数据的区域,这个区域由Garbage Collector进行管理,Garbage Collector在JVM启动时可以指定。JVM调优一般都围绕着修改Heap的大小和选择最合适的Garbage Collector。而JIT Compiler虽然也会对应用的性能有大的影响,但是新版本的JVM是不需要进行优化的。


什么时候需要JVM调优


从表象来看,当应用的响应慢或者已经不能提供服务了,或应用吞吐量小,占用内存空间过大,我们就需要对应用进行调优了。这些表象一般伴随着频繁的垃圾回收,或者OOM

 

JVM调优的指标一般有3个

 

应用占用的内存

 

主要是分配给jvm的堆内存,由启动jvm-Xms-Xmx参数指定,分别是jvm启动时分配的内存和运行时可以分配的最大内存。

 

吞吐量

 

比如每秒钟处理的事务数量,每小时完成的跑批任务数,每小时完请求数据库成功的数量。

 

响应延迟

 

从应用收到请求到返回响应所耗费的时间,或者浏览器发出请求到页面渲染的时间。


常见的OOM异常及复现方法


OOM是我们程序员最不想看到的异常,但是时常发生在我们的工作中。在jvm没有足够内存为新创建的对象分配空间,并且没有足够内存为垃圾收集器使用时就会触发,java应用就会触发OOM。当然,linux本身也有OOM killer机制,当内核监控到进程占用空间过大时,尤其是内存瞬间增大时,为了防止耗尽内存,会触发OOM杀死进程。Java中常见的OOM如下:

java.lang.OutOfMemoryError: Java heap space


这个异常的原因无非2个,内存泄漏和内存溢出。内存溢出的时候,需要调整JVM参数-Xmx配置,调大堆空间,如果是内存泄漏,就需要找出泄漏的代码,这个见后面监控工具讲解。

下面代码是一段典型的内存泄漏的代码,启动时设置-Xmx512m

public class HeapSize {
    public static void main(String[] args){
        List<User> list = new ArrayList<>();
        User user = new User();
        while (true){
            list.add(user);
        }
    }
}

执行后等待一段时间:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at boot.oom.HeapSize.main(HeapSize.java:18)

java.lang.OutOfMemoryError: GC overhead limit exceeded


这种异常的原因是垃圾收集器GC效率很低,jvm花费超过 98%的 CPU 时间来进行一次 GC,但是回收的内存少于 2%的堆空间大小,并且GC连续超过5次都这样

public class GcOverrhead {
    public static void main(String[] args){
        Map map = System.getProperties();
        Random r = new Random();
        while (true) {
            map.put(r.nextInt(), "value");
        }
    }
}

上面代码启动时加参数:-Xmx45m -XX:+UseParallelGC -XX:+PrintGCDetails运行一段时间,就会出现以下异常,注意:这个参数只是作者本地的环境,需要根据自己环境相应修改

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Hashtable.addEntry(Hashtable.java:435)
at java.util.Hashtable.put(Hashtable.java:476)
at boot.oom.HeapSize.main(HeapSize.java:20)

通过增加参数-XX:-UseGCOverheadLimit可以避免这个异常,但其实是自己骗自己,还是需要实际去定位解决问题。


java.lang.OutOfMemoryError: Requested array size exceeds VM limit


这个异常很容易理解,请求分配的数组大小超过jvm限制,出现这种情况的原因有2:

请求分配的数组太大,导致jvm空间不足

请求的数组大于等于Integer.MAX_INT - 1

 

如下2段代码:

这段代码直接抛出Requested array size exceeds VM limit

int[] arr = new int[Integer.MAX_VALUE - 1];

这段代码先抛出 Java heap space后再抛出Requested array size exceeds VM limit

for (int i = 3; i >= 0; i--) {
    try {
        int[] arr = new int[Integer.MAX_VALUE-i];
        System.out.format("Successfully initialized an array with %,d elements.\n", Integer.MAX_VALUE-i);
    } catch (Throwable t) {
        t.printStackTrace();
    }
}

结果如下:

java.lang.OutOfMemoryError: Java heap space
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)
java.lang.OutOfMemoryError: Java heap space
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)

java.lang.OutOfMemoryError: MetaSpace


元空间在前面已经讲过了,这个异常是元空间不足,解决办法是加大元空间大小,配置参数MaxMetaSpaceSize,我们在启动引用时加入参数:

-XX:MaxMetaspaceSize=2m,直接报错:

Error occurred during initialization of VM
OutOfMemoryError: Metaspace

java.lang.OutOfMemoryError: Request size bytes for reason. Out of swap space


这个异常是操作系统的swap空间不够引起的。我们知道jvm分配的最大内存由Xmx等一些参数指定,如果jvm需要的总内存超出了宿主机可以分配的最大的物理内存,就会用到swap space,如果swap space不足,jvm内存分配就会失败,从而抛出这个异常。这个异常的定位比较复杂,有可能是宿主机上面的其他进程耗用内存太多导致。所以我不建议用增加swap space这种粗暴的方式,禁用swap,做好进程的隔离是比较妥当的解决方案。

 

java.lang.OutOfMemoryError: Unable to create native threads


这个异常也是操作系统级别的。大家知道,java的线程是操作系统级别的,java每申请一个线程,就需要调用操作系统创建一个本地的线程,操作系统创建线程失败,会抛出上面的异常。具体原因有以下几种:

a. 内存空间不够,jvm启动时参数-Xss指定每个线程占用的堆栈大小,如果内存不够,就会创建线程失败

b. 操作系统上ulimitmax user processes参数限制,这个参数指操作系统可以创建的全局线程数量

ulimit -a | grep 'max user processes'命令可以查看,如下图:

微信图片_20221212104125.png

ulimit -u可以修改这个参数,比如ulimit -u 10000,则操作系统可以创建10000个线程。

c. 参数sys.kernel.threads-max限制,我们可以通过命令

cat /proc/sys/kernel/threads-max来查看,如下图:

微信图片_20221212104157.png

想要修改这个参数,需要在/etc/sysctl.conf文件,加入sys.kernel.threads-max = 10000

d. 参数sys.kernel.pid_max限制,这个参数只是每创建一个线程,都需要分配一个pid,当pid的值大于这个值时,就会创建失败。查看命令:cat /proc/sys/kernel/pid_max

微信图片_20221212104232.png


想要修改这个参数,需要在/etc/sysctl.conf文件,加入sys.kernel.pid_max =10000

private static void crateSlowThread(){
        try {
            System.out.println(Thread.currentThread());
            Thread.currentThread().sleep(15000);
        } catch (InterruptedException e)
            e.printStackTrace();
        }
    }
    public static void test1() {
        while (true){
            new Thread(() -> crateSlowThread()).start();
        }
    }

看上面这个代码,在死循环内部不停地创建线程,最后就会复现这个OOM:见下图:

微信图片_20221212104321.png

下面这段代码模拟高并发

public static void test() {
        for (int i = 0; i < 20; i ++){
            System.out.println(Thread.currentThread());
            new Thread(() -> crateSlowThread()).start();
        }
    }
    private static void crateSlowThread(){
        try {
            System.out.println(Thread.currentThread());
            Thread.currentThread().sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @RequestMapping("/createNativeThreads1")
    public String createNativeThreads1(){
        System.out.println("createNativeThreads test1");
        CreateNativeThreads.test1();
        return "Sucess!";
    }

在jmeter中进行测试: 

微信图片_20221212104407.png

JVM自带监控工具

JPS列出目标虚拟机上的所有进程

使用示例:jps -mlvV

微信图片_20221212104434.png

主要参数

-m 打印传递给主类的参数

-l 打印模块名以及包名

-v 打印jvm启动参数,比如-XX:+HeapDumpOnOutOfMemoryError

-V 输出通过标记的文件传递给JVM的参数

 jstat主要监控虚拟机的性能数据


使用示例:jstat -gc -h 2 44074 1s 5

微信图片_20221212104511.png

基本参数:

-t展示从虚拟机运行到现在的性能数据

-h n n大于0是每隔几行展示行头部信息

vmid 展示虚拟机表示

interval 展示性能采样数据的间隔时间

count 展示性能指标的次数

性能参数:

Class 类加载器统计信息

Compiler 即时编译器统计信息

Gc 堆垃圾回收信息

Gccapacity 各代的空间信息

Gccause 同gcutil

Gcnew 新生代统计信息

Gcnewcapacity 展示新生代空间占用情况

Gcold 老年代统计信息

Gcoldcapacity 展示老年代空间占用情况

Gcmetacapacitymeta space 空间大小信息

Gcutil 统计垃圾收集汇总信息

PrintcompilationDisplays Java HotSpot VM compilation method statistics.

 

 jmap展示指定进程的对象共享内存或堆内存信息


使用示例:

将堆中所有存活对象导出

jmap -dump:live,format=b,file=filename.bin

打印堆中存活对象

jmap -histo:live 44074

主要参数如下:

-clstats 展示被加载类的信息

-finalizerinfo 展示所有待 finalize 的对象

-histo 展示各个类的实例数目以及占用内存,并按照内存使用量从多至少的顺序排列

-histo:live 展示堆中的存活对象

-dump 导出 Java 虚拟机堆的快照

-dump:live 只保存堆中的存活对象

当系统OOM后,如果服务已经挂了,或者监控系统监控到服务关闭后重启,时候通过 jmap命令已经不能导出堆快照了,所以我们需要在启动虚拟机时加入下面2个参数:

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=\dump

jinfo查看或修改Java 进程的参数


使用示例:

展示参数配置信息jinfo 44074

修改进程参数jinfo -flag +HeapDumpAfterFullGC 44074

主要参数:

-flag name 打印参数名是name的参数值

-flag [+|-]name 更改bool类型参数值

-flag name=value 增加参数对

-flags 打印传递给jvm的参数对

-sysprops 打印java系统参数对

jstack打印java进程的线程栈信息,已经线程持有的锁


示例:jstack 44074

输出如下:

微信图片_20221212104600.png

常用参数:

-F -l参数无响应是强制打印快照信息

-l 打印有关锁的额外信息比如Locked ownable synchronizers

-m Prints a mixed mode stack trace that has both Java and native C/C++ frames.

 

JVM常用调优参数


堆空间设置:

-Xmx4g 进程占用的最大堆空间大小,超出后会OOM

-Xms2g 初始化堆空间大小

-Xmn1g 年轻代大小,官方推荐配置为整个堆的3/8

-XX:NewRatio=n 年轻代和老年代空间大小比值

-Xss512k 每个线程占用内存大小

-XX:SurvivorRatio=n:年轻代中Eden区与Survivor区的比值。比如n=4,则EdenSurvivor比值为4:2survivor占年轻代一半

-XX:MetaspaceSize=512m 元空间大小

-XX:MaxMetaspaceSize=512m 这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存

-XX:MinMetaspaceFreeRatio=N

当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。在本机该参数的默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存

-XX:MaxMetasaceFreeRatio=N

当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%

-XX:MaxMetaspaceExpansion=N Metaspace增长时的最大幅度

 

垃圾收集器设置

-XX:+UseSerialGC 设置串行收集器

-XX:+UseParallelGC 设置并行收集器

-XX:+UseParalledlOldGC 设置并行年老代收集器

-XX:+UseConcMarkSweepGC 设置并发收集器

-XX:ParallelGCThreads=n 设置并行收集器收集时使用的线程数

-XX:MaxGCPauseMillis=n 设置并行收集最大暂停时间

-XX:GCTimeRatio=n 设置垃圾回收时间占程序运行时间的百分比,1/(1+n)

-XX:+DisableExplicitGC 禁止外部调用System.gc()

-XX:MaxTenuringThreshold 年轻代复制多少次才会进入老年代

 

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps 打印每次垃圾回收前,程序未中断的执行时间

-Xloggc:filename 把gc日志存入文件

-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期间程序暂停的时间

-XX:+PrintGCApplicationConcurrentTime 打印每次垃圾回收前,程序未中断的执行时间

-XX:+PrintHeapAtGC 打印GC前后的详细堆栈信息

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/dump

JVM第三方监控工具

eclipse mat

下载地址:https://www.eclipse.org/mat/downloads.php

eclipse mat是分析java应用非常常用的工具,可以集成在eclipse,也可以单独安装。我们还是以之前的一个OOM异常案例来介绍

public static void test(){
        List<User> list = new ArrayList<>();
        User user = new User();
        while (true){
            list.add(user);
        }
    }

启动应用命令:


java -jar -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -

启动后调用这个方法,程序抛出了OOM,生成了堆转存文件:java_pid46242.hprof,接着我们打开mat工具,导入刚刚的对转存文件,如下图:

微信图片_20221212104718.png

MAT 计算对象占据内存方式有2种。第一种是 Shallow heap,统计对象占用内存。第二种是 Retained heap,统计当对象不再被引用时,垃圾回收器所能回收的总内存,包括对象自身所占据的内存,以及仅能够通过该对象引用到的其他对象所占据的内存。上面的饼状图便是基于 Retained heap 的。

从上面的报告中,我们看出有内存泄漏的情况,点进去后就能找到内存泄漏点,如下图:

微信图片_20221212104740.png

阿里诊断工具 Arthas


工具地址:

https://alibaba.github.io/arthas/arthas-tutorials?language=cn&id=arthas-basics
https://alibaba.github.io/arthas/arthas-tutorials?language=cn&id=arthas-advanced

使用场景:

找到类所在jar包

找出异常的原因

找到代码没有执行到的原因

线上debug

全局监控系统状态

实时监控 JVM 运行状态


IBM heap anolyzer


这个工具是找出堆内存泄漏的一款图形化工具,界面如下:

官网地址:

https://www.ibm.com/support/pages/ibm-heapanalyzer

这款工具目前IBM已经不再更新,并且官网推荐使用MAT


调优案例


死锁诊断


下面代码是一段经典的死锁案例

public static void test() {
        Object lockA = new Object();
        Object lockB = new Object();
        new Thread(() ->{
            synchronized (lockA){
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (lockB){
                    System.out.println("thread 1");
                }
            }
        }).start();
        new Thread(() ->{
            synchronized (lockB){
                synchronized (lockA){
                    System.out.println("thread 2");
                }
            }
        }).start();
    }

在main函数启动后一直不能执行结束,用http方式调用这个方法,发现一直没有返回结果。输入命令:jstack 45309 > deadlock.txt 然后查看生产的文件,能看到BOLOCKED状态的线程,如下图:

微信图片_20221212104854.png

堆内存参数设置


我们在java应用启动时加入下面2个参数,就会在日志里面打印详细的垃圾收集信息

-XX:+PrintGC

-XX:+PrintGCDetails

如下是一个Full GC的日志,我们来分析一下


[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(150528K)] [ParOldGen: 243998K->142439K(172032K)] 243998K->142439K(322560K), [Metaspace: 47754K->47754K(1093632K)], 3.6879500 secs] [Times: user=3.91 sys=0.00, real=3.69 secs]

Full GC:表示是一个Full GC垃圾回收,如果不带Full,那就表示是Minor GC


Allocation Failure:本次垃圾回收的原因是因为年轻代没有足够的内存分配给新的对象

[PSYoungGen: 0K->0K(150528K)]:这3个数值分别代表年轻代垃圾收集前占用的堆内存大小,年轻代垃圾收集后占用的堆内存大小,年轻代占用堆内存总大小

[ParOldGen: 243998K->142439K(172032K)]:这3个数值分别代表老年代垃圾收集前占用的堆内存大小,老年代垃圾收集后占用的堆内存大小,老年代占用堆内存总大小

243998K->142439K(322560K):这2个数值分别代表堆内存垃圾收集前使用量,堆内存垃圾收集后使用量,堆空间总大小

[Metaspace: 47754K->47754K(1093632K)]:这3个数值分别代表元空间垃圾收集前占用的内存大小,元空间垃圾收集后占用的内存大小,元空间总大小

3.6879500 secs:本次GC持续的时间

[Times: user=3.91 sys=0.00, real=3.69 secs]:这3个时间表示GC线程消耗的CPU时间,GC过程系统调用和等待花费的时间,应用程序暂停的时间。

 

堆内存大小设置


上面分析可知,老年代垃圾收集后占用的堆内存大小是142439K=139M

我们根据这个数值来指定堆空间大小,我们的应用中建议-Xms和-Xmx参数设置为一样大小,这样可以减少启动初期的GC次数,同时避免JVM在运行过程中向OS申请内存。这2个参数建议设置为老年代垃圾收集后占用的堆内存大小的3~4倍,本案例中即139M*(3~4),官方建议年轻代设置为堆内存总大小的3/8,所以年轻代大小为-Xmn139M*(3~4) * 3/8


元空间大小设置


上面分析可知,元空间垃圾收集后占用的内存大小是47754K=47M

-XX:MetaspaceSize -XX:MaxMetaspaceSize这2个值建议设置为上面值的1.2~1.5倍,即47M * (1.2~1.5)

 

综上取最大分析值,启动参数为:

java -jar -Xms556m -Xmx556m -Xmn208m -XX:MetaspaceSize=70m -XX:MaxMetaspaceSize=70m -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ spring-boot-mybatis-1.0-SNAPSHOT.jar

 垃圾收集时间

对于Minor GC耗费时间跟年轻代空间大小成正比,Minor GC触发频率跟年轻代空间大小成反比。示例如下图:

微信图片_20221212104950.png

我们在日志中取样,在10s中时间内Minor GC触发了8次,频率为 次/0.8s,8GC的平均时间为:0.05s=50ms

如果我们系统调优指标是40ms,那就需要减小年轻代大小,上面案例中,我们年轻代大小减少20%208m * 80%


最后,JVM调优是一个永久的话题,本人能力有限,欢迎大家批评指正


参考文章:

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html
https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html#t1s2
https://plumbr.io/outofmemoryerror
http://openjdk.java.net/jeps/122

文中源代码地址:

    https://github.com/jinjunzhu/spring-boot-mybatis
    相关文章
    |
    3月前
    |
    算法 Java 关系型数据库
    掌握这3个技巧,你也可以秒懂JAVA性能调优和jvm垃圾回收
    JVM 是一个虚拟化的操作系统,类似于 Linux 和 Window,只是他被架构在了操作系统上进行接收 class 文件并把 class 翻译成系统识别的机器码进行执行,即 JVM 为我们屏蔽了不同操作系统在底层硬件和操作指令的不同。
    27 0
    |
    4月前
    |
    缓存 Java 中间件
    jvm性能调优实战 -55RPC调用引发的OOM故障
    jvm性能调优实战 -55RPC调用引发的OOM故障
    59 0
    |
    4月前
    |
    缓存 监控 Java
    jvm性能调优实战 - 48无限循环调用和没有缓存的动态代理引起的OOM
    jvm性能调优实战 - 48无限循环调用和没有缓存的动态代理引起的OOM
    43 0
    |
    4月前
    |
    消息中间件 存储 Java
    jvm性能调优实战 - 47超大数据量处理系统是如何OOM的
    jvm性能调优实战 - 47超大数据量处理系统是如何OOM的
    46 0
    |
    4月前
    |
    Java
    jvm性能调优实战 - 46堆区OOM解析
    jvm性能调优实战 - 46堆区OOM解析
    46 0
    |
    4月前
    |
    缓存 监控 算法
    jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
    jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
    77 0
    |
    4月前
    |
    架构师 Java
    jvm性能调优实战 - 35电商APP后台系统如何对Full GC进行深度优化
    jvm性能调优实战 - 35电商APP后台系统如何对Full GC进行深度优化
    53 0
    |
    4月前
    |
    监控 数据可视化 Java
    jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
    jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
    54 1
    |
    4月前
    |
    监控 Java
    jvm性能调优实战 - 24模拟因动态年龄判断对象进入老年代的场景
    jvm性能调优实战 - 24模拟因动态年龄判断对象进入老年代的场景
    49 0
    |
    7天前
    |
    Arthas 人工智能 监控
    JVM工作原理与实战(三十五):性能调优
    JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了性能调优、性能调优案例等内容。
    17 1