【JVM调优实战100例】03——JVM堆调优四例

简介: 【JVM调优实战100例】03——JVM堆调优四例

6.堆

6.1 堆的特点

使用new关键字创建的对象都会使用堆。

特点:

  • 线程共享,堆中的对象需要考虑线程安全问题。
  • 具有垃圾回收机制。

6.2 堆内存溢出问题

堆中具有垃圾回收机制,但是垃圾回收的前提是堆中的对象不再被引用(实际上,回收引用的算法是根可达算法,后面会讲述,这里的表述是不准确的),因此如果我们有过多无法被回收的对象,就可能导致内存溢出。

public class MemoryOverFlow {
    public static void main(String[] args) {
        int i = 0;
        String a = "hello";
        List list = new ArrayList(); // 直到catch代码块执行,一直被使用
        try {
            while (true) {
                list.add(a);
                a = a + a;
                i++;
            }
        } catch (Throwable e) { //使用Throwable,如果使用Exception包不住Error,i无法被打印出来
           e.printStackTrace();
           System.out.println(i);
        }
    }
}

出现OutOfMemoryError

java.lang.OutOfMemoryError: Overflow: String length out of range
        at java.base/java.lang.StringConcatHelper.checkOverflow(StringConcatHelper.java:57)
        at java.base/java.lang.StringConcatHelper.mix(StringConcatHelper.java:138)
        at java.base/java.lang.StringConcatHelper.simpleConcat(StringConcatHelper.java:420)
        at MemoryOverFlow.main(MemoryOverFlow.java:12)
28

另:参数-Xmx 可以设置jvm内存空间大小,排查堆内存问题时可以将其设置得比较小(如8m),更容易暴露出内存溢出问题。设置方法:点击build and run同行的modify options->add vm options.

6ede40ab8bc146118e1aa61c7c8e9c49.png

6.3 代码内存性能影响的评估

工作中编写了一段代码,如何去判断一段代码对于内存性能的影响呢?可以借助如下工具。

  • jps 查看系统有哪些java进程
  • jmap 查看某一时刻堆内存的占用情况
  • jconsole 多功能实时监测工具

通过下面的demo来演示。

public class jvmdemo {
  public static void main(String[] args) throws InterruptedException {
        System.out.println("1....."); //输出提示,方便进行Heap Dump
        Thread.sleep(60000); //给30s时间用于Heap Dump
        byte [] arr = new byte[1024 * 1024 * 10];
        System.out.println("2.......");
        Thread.sleep(60000);
        arr = null;
        System.gc();
        System.out.println("3......");
        Thread.sleep(100000L);
  }
}

当输出1…后,先执行jps查看jvmdemo对应的pid

💡 tip:如果您是windows系统,jps无返回结果,可以参考博客

Windows中jps命令无法查看java进程问题_无数_mirage的博客-CSDN博客_windows 查看java进程

结果如下。

9a225e829ab34c7583328a3aec738014.png

执行jmap -heap xxx(pid)查看此时堆内存占用情况。

💡 tip:

如果执行jmap报错。

Error: -heap option used
Cannot connect to core dump or remote debug server. Use jhsdb jmap instead

是因为jdk8之后的版本之前的jmap -heap xxx(pid)命令不可再使用。可以改用命令jhsdb jmap --heap --pid xxx.

在提示信息输出1,2,3后分别进行三次操作得到的结果如下。

Heap Usage:
G1 Heap:
   regions  = 2034
   capacity = 8531214336 (8136.0MB)
   used     = 0 (0.0MB)
   free     = 8531214336 (8136.0MB)
   0.0% used
G1 Young Generation:
Eden Space:
   regions  = 0
   capacity = 29360128 (28.0MB)
   used     = 0 (0.0MB)
   free     = 29360128 (28.0MB)
   0.0% used
Survivor Space:
   regions  = 0
   capacity = 0 (0.0MB)
   used     = 0 (0.0MB)
   free     = 0 (0.0MB)
   0.0% used
G1 Old Generation:
   regions  = 0
   capacity = 507510784 (484.0MB)
   used     = 0 (0.0MB)
   free     = 507510784 (484.0MB)
   0.0% used
Heap Usage:
G1 Heap:
   regions  = 2034
   capacity = 8531214336 (8136.0MB)
   used     = 12582912 (12.0MB)
   free     = 8518631424 (8124.0MB)
   0.14749262536873156% used
G1 Young Generation:
Eden Space:
   regions  = 0
   capacity = 29360128 (28.0MB)
   used     = 0 (0.0MB)
   free     = 29360128 (28.0MB)
   0.0% used
Survivor Space:
   regions  = 0
   capacity = 0 (0.0MB)
   used     = 0 (0.0MB)
   free     = 0 (0.0MB)
   0.0% used
G1 Old Generation:
   regions  = 3
   capacity = 507510784 (484.0MB)
   used     = 12582912 (12.0MB)
   free     = 494927872 (472.0MB)
   2.479338842975207% used
Heap Usage:
G1 Heap:
   regions  = 2034
   capacity = 8531214336 (8136.0MB)
   used     = 673872 (0.6426544189453125MB)
   free     = 8530540464 (8135.357345581055MB)
   0.007898898954588403% used
G1 Young Generation:
Eden Space:
   regions  = 0
   capacity = 8388608 (8.0MB)
   used     = 0 (0.0MB)
   free     = 8388608 (8.0MB)
   0.0% used
Survivor Space:
   regions  = 0
   capacity = 0 (0.0MB)
   used     = 0 (0.0MB)
   free     = 0 (0.0MB)
   0.0% used
G1 Old Generation:
   regions  = 1
   capacity = 8388608 (8.0MB)
   used     = 673872 (0.6426544189453125MB)
   free     = 7714736 (7.3573455810546875MB)
   8.033180236816406% used

重点查看uesd这一项,可以看到代码中内存的变化过程,这里JVM version 是16.0.2+7-67,不同版本可能略有差异。

使用jconsole可以实时观测数据,而且不仅仅可以观测本地进程,还可以观测远程进程。

使用方法很简单。在命令终端输入jconsole就会自动弹出来。


a28ac5eec6a14bf48a025c5402faba9c.png

上面代码的观测结果如下。

e01f38a8c1f549509cccbccac5f6dbbb.png

📕 总结:

上面在排查问题时,我们通过加log以及sleep的方式,帮助我们进行dump时间的把握,在实际生产中可以运用类似方式。

除了内存,jconsole还可以监测线程、cpu占用率以及类的数量变化等。

还可以帮我们检测死锁。


22c0be6b1e6f403690effc80a06480de.png

6.4 多次垃圾回收内存占用仍很高问题的排查

jvisualvm也是一个可视化工具,比jconsole更好用。

使用方法很简单,使用命令which java查找到java安装路径,并切换至该路径,然后在对应路径执行jvisualvm即可。

在高版本JDK(大于1.8或后期更新的1.8版本)中已经不会再自动集成它了。参考博客可以下载独立版:JDK 高版本没有VisualVM_东理羁客的博客-CSDN博客

下面使用它演示,多次垃圾回收内存占用仍很高问题的排查

/**
 * 演示查看对象个数 堆转储 dump
 */
public class Demo1_13 {
    public static void main(String[] args) throws InterruptedException {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            students.add(new Student());
//            Student student = new Student();
        }
        Thread.sleep(1000000000L);
    }
}
class Student {
    private byte[] big = new byte[1024*1024];
}

使用jvisualvm点击选择对应的进程,可以看到堆内存占用很高,执行垃圾回收。

bb2046552ba0425199dd9652f4aaf7d2.png

内存并没有减少很多。


5d572c46f9e0483a8e85a9c987579ed6.png

进行Heap Dump,选择右侧菜单栏,按照占用内存大小对class进行排序


d8e3f21898794b77ba94089a501f9738.png

按照占用内存大小对class进行排序结果如下。


2e8ff999541744f78ccd4460653d4711.png

点击类即可看到对应类的所有实例,我们点击占用内存最高的ArrayList查看实例,点击实例中的elementData即可查看详情。


4d691416ec80428c94603441ce801099.png

发现是可能是student的锅,可以看若干适量实例,确认结论。

5443989a6cd546249338e36c44a4b55b.png

再点student对象,定位到属性big。发现一个属性占用了大约1M的空间。这忍不了

88d30af99a0040128593cd5045679632.png


然后定位到对应的源码。

/**
 * 演示查看对象个数 堆转储 dump
 */
public class Demo1_13 {
    public static void main(String[] args) throws InterruptedException {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            students.add(new Student());
//            Student student = new Student();
        }
        Thread.sleep(1000000000L);
    }
}
class Student {
    private byte[] big = new byte[1024*1024];
}

果然,我们有200个Student对象,持有了内存占用1M的big实例200个,并且一直处于生存中。

相关文章
|
24天前
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
51 10
|
1月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
2月前
|
存储 监控 Java
合理设置JVM堆大小
合理设置JVM堆大小
46 4
|
2月前
|
监控 Java 编译器
Java虚拟机调优指南####
本文深入探讨了Java虚拟机(JVM)调优的精髓,从内存管理、垃圾回收到性能监控等多个维度出发,为开发者提供了一系列实用的调优策略。通过优化配置与参数调整,旨在帮助读者提升Java应用的运行效率和稳定性,确保其在高并发、大数据量场景下依然能够保持高效运作。 ####
37 1
|
2月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
2月前
|
监控 Java 测试技术
Elasticsearch集群JVM调优垃圾回收器的选择
Elasticsearch集群JVM调优垃圾回收器的选择
67 1
|
2月前
|
存储 IDE Java
实战优化公司线上系统JVM:从基础到高级
【11月更文挑战第28天】Java虚拟机(JVM)是Java语言的核心组件,它使得Java程序能够实现“一次编写,到处运行”的跨平台特性。在现代应用程序中,JVM的性能和稳定性直接影响到系统的整体表现。本文将深入探讨JVM的基础知识、基本特点、定义、发展历史、主要概念、调试工具、内存管理、垃圾回收、性能调优等方面,并提供一个实际的问题demo,使用IntelliJ IDEA工具进行调试演示。
46 0
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
388 1
|
12天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。