JVM:JVM调优

文章目录

一、内存调优

1、什么是内存泄漏

  • 在Java中如果不在使用一个对象,但是该对象依然在GC ROOT的引用链上,这个对象就不会被垃圾回收器回收,这种情况就称为内存泄漏。
  • 内存溢出绝大多数情况都是由堆内存泄漏引起的,所以后续没有特别说明则讨论的都是堆内存泄漏。但是产生内存溢出并不是只有内存泄漏这一种原因。

2、监控Java内存的常用工具

(1) Top命令

  • top命令是linux下用来查看系统信息的一个命令,它提供给我们去实时地去查看系统的资源,比如执行时的进程、线程和系统参数等信息。
  • 进程使用的内存为RES(常驻内存)、SHR(共享内存)。

只能查看最基础的进程信息,无法查看每个部分的内存占用(堆、方法区、堆外)。

(2)VisualVM

  • VisualVm是多功能合一的Java故障排除工具并且它是一款可视化工具,整合了命令行JDK工具和轻量化分析功能,功能非常强大。
  • 这款软件在Oracle JDK 6~8中发布,但是在Oracle JDK 9之后不在 JDK安装目录下需要单独下载,下载地址:https://visualvm.gthub.io/

(3)Arthas

Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行判断,包括查看方法调用出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上排查问题效率。

  • 使用arthas tunnel管理所有的需要监控的程序。

(4)Prometheus + Grafana

Prometheus + Grafana是企业中运维常用的监控方案,其中Prometheus用来采集系统或者应用的相关数据,同时具备告警功能。Grafana可以将Prometheus采集到的数据以可视化的方式展示。

优点

  • 支持系统级别和应用级别的监控,比如linux操作系统、Redis、MySQL、Java进程。
  • 支持告警并允许自定义告警指标,通过邮件、短信等方式尽早通知相关人员进行处理。

缺点

  • 环境搭建较为复杂,一般有运维人员完成。

(5)MAT

  • 当堆内存溢出时,需要在堆内存溢出时将整个堆内存保存下来,生成内存快照(Heap Profile)文件。
  • 使用MAT打开hprof文件,并选择内存泄漏检测功能,MAT会自行根据内存快照中保存的数据分析内存泄漏的根源。
  • 当堆内存溢出时,需要在堆内存溢出时将整个堆内存保存下来,生成内存快照(Heap Profile)文件。
  • 生成内存快照的Java虚拟机参数:
    • -XX:+HeapDumpOnOutOfMemoryError, 发生OutOfMemoryError错误时,自动生成hprof内存快照文件。
    • -XX:HeapDumpPath= :指定hprof文件的输出路径。
  • 使用MAT打开hprof文件,并选择内存泄漏检测功能,MAT会自行根据内存快照中保存的数据分析内存泄漏的根源。

3、内存泄漏的常见场景

  • 内存泄漏导致溢出的常见场景是大型的Java后端应用中,在处理用户的请求后,没有及时将用户的数据删除。随着用户的请求数量越来越多,内存泄漏的对象占满了堆内存最终导致内存溢出。
  • 这种产生的内存溢出会直接导致用户请求无法处理,影响用户的正常使用。重启可以恢复应用的使用,但是在运行一段时间之后依然会出现内存溢出。

4、内存泄漏产生的原因及解决方案

(1)代码中的内存泄漏

  • equals()和hashCode()

    • 不正确的equals和hashCode实现导致内存泄漏。在定义新类时没有重写正确的equals和hashCode方法,在使用HashMap的场景下,如果使用这个类对象作为key,HashMap在判断key是否已经存在时会使用这些方法,如果重写方式不正确,会导致相同的数据被保存多份。
    • 异常情况
      • hashCode方法实现不正确,会导致相同id的学生对象计算出来的hash值不同,可能被分到不同的槽中。
      • equals方法实现不正确,会导致key在比对时,即便hashCode相同也被认为是不同的key。
    • 解决方案
      • 在定义新实体时,始终重写equals和hashCode方法。
      • 重写时一定要确定使用了唯一标识去区分不同的对象,比如用户的id等。
      • hashmap使用时尽量使用编号id等数据作为key,不要将整个实体类对象作为key存放。
  • 内部类引用外部类

    • 非静态的内部类和匿名内部类的错误使用导致内存泄漏。
    • 问题
      • 非静态的内部类默认会持有外部类,尽管代码上不在使用外部了,所以如果有地方引用了这个非静态内部类,会导致外部类也别引用,垃圾回收时无法回收这个外部类。
      • 匿名内部类对象如果在非静态方法中被创建,会持有调用者对象,垃圾回收时无法回收调用者。
    • 解决方案
      • 第一个案例:使用内部类的原因是可以直接获取到外部类中的成员变量值,简化开发。如果不想持有外部类对象,应该使用静态内部类。
      • 第二个案例:使用静态方法,可以避免匿名内部类持有调用者对象。
  • ThreadLocal的使用

    • 由于线程池中的线程不被回收导致的ThreadLocal内存泄漏。
    • 问题:
      • 如果仅仅使用手动创建线程,就算没有调用ThreadLocal的remove方法清理数据,也不会产生内存泄漏。因为当前线程被回收时,ThreadLocal也同样被回收。但是如果使用线程池就不一定。
    • 解决方案
      • 线程池执行完,一定要调用ThreadLocal中的remove方法清理对象。
  • String的intern方法

    • 由于JDK6中的字符串常量池位于永久代,intern被大量调用并保存产生的内存泄漏。
  • 通过静态字段保存对象

    • 大量的数据在静态变量中被引用,但是不再使用,成为内存泄漏。
    • 问题
      • 如果大量的数据在静态变量中被长期引用,数据就不会被释放,如果这些数据不在使用,就成为了内存泄漏。
    • 解决方案
      • 尽量减少将对象长时间保存在静态变量中,如果不再使用,必须将对象删除(比如在集合中)或者将静态变量设置为null。
      • 使用单例模式时,尽量使用懒加载,而不是立即加载。
      • Spring的Bean中不要长期存放大对象,如果缓存用于提升性能,尽量设置过期时间定时失效。
  • 资源没有正常关闭

    • 由于资源没有调用close方法正常关闭,导致内存泄漏。
    • 问题
      • 连接和流这些资源会占用内存,如果使用完之后没有关闭,这部分内存不一定出现内存泄漏(主要看close方法是否有清理作用),但是会导致close方法不被执行。
    • 解决方案:
      • 为了防止出现这类资源对象泄漏问题,必须在finally块中关闭不再使用的资源。
      • 从Java7开始,使用try-with-resources语法可以用于自动关闭资源。

(2)并发请求问题

  • 并发请求问题指的是用户通过发送请求向Java应用获取数据,正常情况下Java应用将数据返回之后,这部分数据就可以在内存中被释放掉。
  • 并发请求问题指的是用户通过发送请求向Java应用获取数据,正常情况下Java应用将数据返回之后,这部分数据就可以在内存中被释放掉。但是由于用户的并发请求量可能很大,同时处理数据的时间很长,导致大量的数据存在于内存中,最终超过了内存的上限,导致内存溢出。这类问题的处理思路和内存泄漏类似,首先要定位到产生的根源。

二、GC调优

1、如何分析GC日志

2、解决生产环境由于频繁Full GC导致的系统假死问题

三、性能调优

1、使用JMH性能测试框架进行性能测试

2、精准定位线上系统性能问题的根源,进行性能调优

参考:

https://www.bilibili.com/video/BV1r94y1b7eS?p=48\&spm_id_from=pageDriver\&vd_source=cd03889ff27e1a185b3e97e3ed96d260

相关推荐
华仔啊42 分钟前
JVM参数到底配在哪?7大场景全解,新手不再迷茫!
java·jvm
流星5211221 天前
GC 如何判断对象该回收?从可达性分析到回收时机的关键逻辑
java·jvm·笔记·学习·算法
JanelSirry1 天前
我的应用 Full GC 频繁,怎么优化?
jvm
JH30731 天前
jvm,tomcat,spring的bean容器,三者的关系
jvm·spring·tomcat
DKPT1 天前
JVM直接内存和堆内存比例如何设置?
java·jvm·笔记·学习·spring
siriuuus1 天前
JVM 垃圾收集器相关知识总结
java·jvm
小满、1 天前
什么是栈?深入理解 JVM 中的栈结构
java·jvm·1024程序员节
百花~2 天前
JVM(Java虚拟机)~
java·开发语言·jvm
每天进步一点点dlb2 天前
JVM中的垃圾回收算法和垃圾回收器
jvm·算法
漫漫不慢.2 天前
蓝桥杯-16955 岁月流转
java·jvm·蓝桥杯