iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何定位内存泄露
  • 554
分享到

如何定位内存泄露

2023-06-15 15:06:27 554人浏览 薄情痞子
摘要

这篇文章主要讲解了“如何定位内存泄露”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何定位内存泄露”吧!生产-消费者模式简介上一节中我们尝试了多种多线程方案,总会有各种各样奇怪的问题。于是最

这篇文章主要讲解了“如何定位内存泄露”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何定位内存泄露”吧!

生产-消费者模式

简介

上一节中我们尝试了多种多线程方案,总会有各种各样奇怪的问题。

于是最后决定使用生产-消费者模式去实现。

实现如下:

这里使用 AtomicLong 做了一个简单的计数。

userMapper.handle2(Arrays.asList(user)); 这个方法是同事以前的方法,当然做了很多简化。

就没有修改,入参是一个列表。这里为了兼容,使用 Arrays.asList() 简单封装了一下。

import com.GitHub.houbb.thread.demo.dal.entity.User; import com.github.houbb.thread.demo.dal.mapper.UserMapper; import com.github.houbb.thread.demo.service.UserService;  import java.util.Arrays; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong;   public class UserServicePageQueue implements UserService {      // 分页大小     private final int pageSize = 10000;      private static final int THREAD_NUM = 20;      private final Executor executor = Executors.newFixedThreadPool(THREAD_NUM);      private final ArrayBlockingQueue<User> queue = new ArrayBlockingQueue<>(2 * pageSize, true);       // 模拟注入     private UserMapper userMapper = new UserMapper();           private AtomicLong counter = new AtomicLong(0);      // 消费线程任务     public class ConsumerTask implements Runnable {          @Override         public void run() {             while (true) {                 try {                     // 会阻塞直到获取到元素                     User user = queue.take();                     userMapper.handle2(Arrays.asList(user));                      long count = counter.incrementAndGet();                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     }      // 初始化消费者进程     // 启动五个进程去处理     private void startConsumer() {         for(int i = 0; i < THREAD_NUM; i++) {             ConsumerTask task = new ConsumerTask();             executor.execute(task);         }     }           public void handleAllUser() {         // 启动消费者         startConsumer();          // 充值计数器         counter = new AtomicLong(0);          // 分页查询         int total = userMapper.count();         int totalPage = total / pageSize;         for(int i = 1; i <= totalPage; i++) {             // 等待消费者处理已有的信息             awaitQueue(pageSize);              System.out.println(UserMapper.currentTime() + " 第 " + i + " 页查询开始");             List<User> userList = userMapper.selectList(i, pageSize);              // 直接往队列里面扔             queue.addAll(userList);              System.out.println(UserMapper.currentTime() + " 第 " + i + " 页查询全部完成");         }     }           private void awaitQueue(int limit) {         while (true) {             // 获取阻塞队列的大小             int size = queue.size();              if(size >= limit) {                 try {                     // 根据实际的情况进行调整                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             } else {                 break;             }         }     }  }

 测试验证

当然这个方法在集成环境跑没有任何的问题。

于是就开始直接上生产验证,结果开始很快,然后就可以变慢了。

一看 GC 日志,梅开二度,FULL GC。

可恶,圣斗士竟然会被同一招打败 2 次吗?

如何定位内存泄露

FULL GC 的产生

一般要发现 full gc,最直观的感受就是程序很慢。

这时候你就需要添加一下 GC 日志打印,看一下是否有 full gc 即可。

这个最坑的地方就在于,性能问题是测试一般无法验证的,除非你进行压测。

压测还要同时满足两个条件:

(1)数据量足够大,或者说 QPS 足够高。持续压

(2)资源足够少,也就是还想马儿跑,还想马儿不吃草。

好巧不巧,我们同时赶上了两点。

那么问题又来了,如何定位为什么 FULL GC 呢?

内存泄露

程序变慢并不是一开始就慢,而是开始很快,然后变慢,接着就是不停的 FULL GC。

这就和自然的想到是内存泄露。

如何定位内存泄露呢?

你可以分成下面几步:

(1)看代码,是否有明显存在内存泄露的地方。然后修改验证。如果无法解决,则找出可能存在问题的地方,执行第二步。

(2)把 FULL GC 时的堆栈信息 dump 下来,分析到底是什么数据过大,然后结合 1 去解决。

接下来,让我们一起看一下这个过程的简化版本记录。

问题定位

看代码

最基本的生产者-消费者模式确认了即便,感觉没啥问题。

于是就要看一下消费者模式中调用其他人的方法问题。

方法的核心目的

(1)遍历入参列表,执行业务处理。

(2)把当前批次的处理结果写入到文件中。

方法实现

简化版本如下:

 public void handle2(List<User> userList) {     String targetDir = "D:\\data\\";     // 理论让每一个线程只读写属于自己的文件     String fileName = Thread.currentThread().getName()+".txt";     String fullFileName = targetDir + fileName;     FileWriter fileWriter = null;     BufferedWriter bufferedWriter = null;     User userExample;     try {         fileWriter = new FileWriter(fullFileName);         bufferedWriter = new BufferedWriter(fileWriter);         StringBuffer stringBuffer = null;         for(User user : userList) {             stringBuffer = new StringBuffer();              // 业务逻辑             userExample = new User();             userExample.setId(user.getId());             // 如果查询到的结果已存在,则跳过处理             List<User> userCountList = queryUserList(userExample);             if(userCountList != null && userCountList.size() > 0) {                 return;             }             // 其他处理逻辑              // 记录最后的结果             stringBuffer.append("用户")                     .append(user.getId())                     .append("同步结果完成");             bufferedWriter.newLine();             bufferedWriter.write(stringBuffer.toString());         }         // 处理结果写入到文件中         bufferedWriter.newLine();         bufferedWriter.flush();         bufferedWriter.close();         fileWriter.close();     } catch (Exception exception) {         exception.printStackTrace();     } finally {         try {             if (null != bufferedWriter) {                 bufferedWriter.close();             }             if (null != fileWriter) {                 fileWriter.close();             }         } catch (Exception e) {         }     } }

这种代码怎么说呢,大概就是祖传代码吧,不晓得大家有没有见过,或者写过呢?

我们可以不看文件部分,核心部分实际上只有:

User userExample; for(User user : userList) {     // 业务逻辑     userExample = new User();     userExample.setId(user.getId());     // 如果查询到的结果已存在,则跳过处理     List<User> userCountList = queryUserList(userExample);     if(userCountList != null && userCountList.size() > 0) {         return;     }     // 其他处理逻辑 }

 代码存在的问题

你觉得上面的代码有哪些问题?

什么地方可能存在内存泄露呢?

有应该如何改进呢?

看堆栈

如果你看代码已经确定了疑惑的地方,那么接下来就是去看一下堆栈,验证下自己的猜想。

堆栈的查看方式

JVM 堆栈查看的方式很多,我们这里以 jmap 命令为例。

(1)找到 java 进程的 pid

你可以执行 jps 或者 ps ux 等,选择一个你喜欢的。

我们 windows 本地测试了下(实际生产一般是 linux 系统):

D:\Program Files\Java\jdk1.8.0_192\bin>jps 11168 Jps 3440 RemoteMavenServer36 4512 11660 Launcher 11964 UserServicePageQueue

UserServicePageQueue 是我们执行的测试程序,所以 pid 是 11964

(2)执行 jmap 获取堆栈信息

命令:

jmap -histo 11964

效果如下:

D:\Program Files\Java\jdk1.8.0_192\bin>jmap -histo 11964   num     #instances         #bytes  class name ----------------------------------------------    1:        161031       20851264  [C    2:        157949        3790776  java.lang.String    3:          1709        3699696  [B    4:          3472        3688440  [I    5:        139358        3344592  com.github.houbb.thread.demo.dal.entity.User    6:        139614        2233824  java.lang.Integer    7:         12716         508640  java.io.FileDescriptor    8:         12714         406848  java.io.FileOutputStream    9:          7122         284880  java.lang.ref.Finalizer   10:         12875         206000  java.lang.Object   ...

当然下面还有很多,你可以使用 head 命令过滤。

当然,如果服务器不支持这个命令,你可以把堆栈信息输出到文件中:

jmap -histo 11964 >> dump.txt

堆栈分析

我们可以很明显发现不合理的地方:

[C 这里指的是 chars,有 161031。

String 是字符串,有 157949。

当然还有 User 对象,有 139358。

我们每一次分页是 1W 个,queue 中最多是 19999 个,这么多对象显然不合理。

代码中的问题

chars 和 String 为什么这么多

代码给人的第一感受,就是和业务逻辑没啥关系的写文件了。

很多小伙伴肯定想到了可以使用 TWR 简化一下代码,不过这里存在两个问题:

(1)最后文件中能记录所有的执行结果吗?

(2)有没有更好的方式呢?

对于问题1,答案是不能。虽然我们为每一个线程创建一个文件,但是实际测试,发现文件会被覆盖。

实际上比起我们自己写文件,更应该使用 log 去记录结果,这样更加优雅。

于是,最后把代码简化如下:

//日志  User userExample; for(User user : userList) {     // 业务逻辑     userExample = new User();     userExample.setId(user.getId());     // 如果查询到的结果已存在,则跳过处理     List<User> userCountList = queryUserList(userExample);     if(userCountList != null && userCountList.size() > 0) {         // 日志         return;     }     // 其他处理逻辑      // 日志记录结果 }

user 对象为什么这里多?

我们看一下核心业务代码:

User userExample; for(User user : userList) {     // 业务逻辑     userExample = new User();     userExample.setId(user.getId());     // 如果查询到的结果已存在,则跳过处理     List<User> userCountList = queryUserList(userExample);     if(userCountList != null && userCountList.size() > 0) {         return;     }     // 其他处理逻辑 }

这里在判断是否存在的时候构建了一个 mybatis 中常用的 User 查询条件,然后判断查询的列表大小。

这里有两个问题:

(1)判断是否存在,最好使用 count,而不是判断列表结果大小。

(2)User userExample 的作用域尽量小一点。

调整如下:

for(User user : userList) {     // 业务逻辑     User userExample = new User();     userExample.setId(user.getId());     // 如果查询到的结果已存在,则跳过处理     int count = selectCount(userExample);     if(count > 0) {         return;     }     // 其他业务逻辑 }

 调整之后的代码

这里的 System.out.println 实际使用时用 log 替代,这里只是为了演示。

 public void handle3(List<User> userList) {     System.out.println("入参:" + userList);     for(User user : userList) {         // 业务逻辑         User userExample = new User();         userExample.setId(user.getId());         // 如果查询到的结果已存在,则跳过处理         int count = selectCount(userExample);         if(count > 0) {             System.out.println("如果查询到的结果已存在,则跳过处理");             continue;         }         // 其他业务逻辑         System.out.println("业务逻辑处理结果");     } }

 生产验证

全部改完之后,重新部署验证,一切顺利。

感谢各位的阅读,以上就是“如何定位内存泄露”的内容了,经过本文的学习后,相信大家对如何定位内存泄露这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: 如何定位内存泄露

本文链接: https://www.lsjlt.com/news/280592.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何定位内存泄露
    这篇文章主要讲解了“如何定位内存泄露”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何定位内存泄露”吧!生产-消费者模式简介上一节中我们尝试了多种多线程方案,总会有各种各样奇怪的问题。于是最...
    99+
    2023-06-15
  • linux内存泄露问题怎么定位
    定位 Linux 内存泄漏问题可以采取以下几种方法: 使用top命令或htop命令查看进程的内存使用情况,观察内存占用的增长情况...
    99+
    2024-02-29
    linux
  • 定位并修复 Go 中的内存泄露问题
    Go 是一门带 GC 的语言,因此,大家很容易认为它不会有内存泄露问题。 大部分时候确实不会,但如果有些时候使用不注意,也会导致泄露。 本文案例来自谷歌云的代码,探讨如何找到并修复 ...
    99+
    2024-04-02
  • win11内存泄露如何解决
    这篇文章主要介绍“win11内存泄露如何解决”,在日常操作中,相信很多人在win11内存泄露如何解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”win11内存泄露如何解决”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-30
  • 如何解决定位并修复Go 中的内存泄露问题
    这篇文章将为大家详细讲解有关如何解决定位并修复Go 中的内存泄露问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Google Cloud Go 客户端库 [1] 通常在后台使用 gRPC 来连接 Goo...
    99+
    2023-06-25
  • ehcache内存泄露如何解决
    解决Ehcache内存泄漏的问题可以尝试以下几个方法:1. 升级Ehcache版本:确保使用的是最新的Ehcache版本,因为较新的...
    99+
    2023-09-13
    ehcache
  • php内存泄露如何排查
    要排查PHP内存泄露问题,可以采取以下几个步骤:1. 使用垃圾回收机制:PHP的垃圾回收机制会自动释放不再使用的内存,可以通过在代码...
    99+
    2023-09-26
    php
  • C++中如何检查内存泄露
    这篇文章给大家介绍C++中如何检查内存泄露,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、前言在Linux平台上 有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器...
    99+
    2023-06-17
  • vue:内存泄露详解
    什么是内存泄露?内存泄露是指new了一块内存,但无法被释放或者被垃圾回收。new了一个对象之后 ,它申请占用了一块堆内存,当把这个对象指针置为null时或者离开作用域导致被销毁,...
    99+
    2024-04-02
  • python外篇(内存泄露)
    目录 了解 循环引用造成的内存泄露 大量创建对象造成的内存泄漏 全局对象造成的内存泄露 不适当缓存造成的内存泄露 内存分析工具  了解 ### 以下为Python中可能会出现内存泄露的情况:     (1) 循环引用:当两个或多个对...
    99+
    2023-09-11
    缓存 python 开发语言 后端
  • c# 内存泄露怎么查
    内存泄露是指应用程序无法释放不再使用的内存。检测 c# 内存泄露的方法包括:1. 使用 visual studio 内存分析器或 jetbrains dotmemory profiler...
    99+
    2024-05-11
    c#
  • Java 中出现内存泄露如何解决
    这期内容当中小编将会给大家带来有关Java 中出现内存泄露如何解决,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。首先,我用下面的命令监视进程:while ( sleep 1&...
    99+
    2023-06-17
  • 浅谈Node的内存泄露
    目录1、node内存相关知识2、哪些情况会造成内存泄露第一、全局变量第二、函数闭包第三、事件监听3、内存泄露的监测4、Chrome DevTools进行分析和对比5、内存分析的意义1...
    99+
    2024-04-02
  • Flex中出现内存泄露如何解决
    Flex中出现内存泄露如何解决,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Flex内存泄露举例:引用泄露:对子对象的引用,外部对本对象或子对象的引用都需要置null;系统类泄...
    99+
    2023-06-17
  • 详解LeakCanary分析内存泄露如何实现
    目录前言LeakCanary的使用LeakCanary原理源码浅析初始化使用总结前言 平时我们都有用到LeakCanary来分析内存泄露的情况,这里可以来看看LeakCanary是如...
    99+
    2022-12-08
    LeakCanary分析内存泄露 LeakCanary 内存泄露
  • Java内存泄露怎么检查
    这篇文章主要介绍“Java内存泄露怎么检查”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java内存泄露怎么检查”文章能帮助大家解决问题。Java是如何管理内存的:Java的内存管理就是对象的分配和...
    99+
    2023-06-03
  • Java 内存泄露问题详解
    目录 1、什么是内存泄露? 2、Java 中可能导致内存泄露的场景 3、长生命周期对象持有短生命周期对象引用造成的内存泄露问题示例 4、静态集合类持有对象引用造成内存泄露问题的示例 1、什么是内存泄露?         内存泄露指的是程...
    99+
    2023-09-08
    Java 内存泄露
  • Android内存泄露怎么解决
    解决Android内存泄露问题的方法有以下几种:1. 避免长生命周期的引用:确保在不使用时及时释放对象的引用,如Activity中的...
    99+
    2023-09-29
    android
  • C#内存泄露问题分析
    这篇文章主要介绍“C#内存泄露问题分析”,在日常操作中,相信很多人在C#内存泄露问题分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#内存泄露问题分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!今天...
    99+
    2023-06-17
  • Linux下如何解决内存统计和内存泄露类问题
    这篇文章主要介绍Linux下如何解决内存统计和内存泄露类问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Linux在内存使用上的原则是:如果内存充足,不用白不用,尽量使用内存来缓存一些文件,从而加快进程的运行速度,...
    99+
    2023-06-16
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作