百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

线上内存溢出分析 内存溢出案例

yuyutoo 2024-10-13 09:13 15 浏览 0 评论

线上内存溢出分析

系统变慢,打开页面变卡,使用top命令查看cpu和内存情况后,发现在使用系统时,CPU 飙高。

针对此问题,展开排查。

排查产生原因

首先,使用 arthas 工具或者使用 jstack 查看 java 运行中的线程状态,查看哪些线程占用cpu过高,在使用时,发现为gc线程。

然后,使用 jstat 或者 arthas 中的 dashboard 命令 进行确认,发现堆中新生代老年代空间已满,每过十几秒就在进行一次full gc。所以,可以分析出CPU飙高的原因为内存溢出。

内存溢出原因分析

因为要进行内存溢出情况,需要拿出堆转储信息,所以,在无人使用系统时,使用 jmap 命令获取堆转储信息:

jmap -dump:format=b,file=heapdump.hprof pid

需要注意的是,使用 jmap 时,会使 JVM 处于 STW(stop the world) 状态,所以尽量不要在生产上有人使用时使用该命令,否则会导致非常长的时间停顿。

在拿到堆转储信息后,使用MAT(Memory Analyzer Tool)工具进行分析,装载文件后,得到如下图中的内容:

发现DefaultSVNRepositoryPoll引出的对象占用了3.6个G的空间(整个JVM分配空间为4个G),所以可以确定,是这个类生产 RunnableSecheduleFuture 对象的问题。

对 DefaultSVNRepositoryPool 进行分析,找问题

在前面已经分析出是DefaultSVNRepositoryPool引出的内存溢出,所以直接在IDEA中搜索到这个类,进入到该类,查看是否有相关 RunnableSecheduleFuture 方法。

  • 首先确认哪里引用到了该类

一切还要从SVN上传文件说起,SVN上传文件有几个步骤:

//1. 判断路径是否存在;
SVNClientManipulator rp = new SVNClientManipulator(user);
//2. 得到版本库;
SVNRepository rps = rp.getRepository(svnDao.getSvnUrlByRepository(repository) + 
repository + "/" + newPath);
//3. 判断是否存在;
SVNNodeKind nodeKind = rps.checkPath("", -1);
if (nodeKind == SVNNodeKind.NONE) {//如果存在则更新路径;
    svnConfigDevelopService.addDir(svnDao.getSvnUrlByRepository(repository) +
 repository + "/" + newPath, desc, rp, true);
}
importFolder = newPath;
// 4. 上传文件
String msg = svnCommonService.addFileToSVN(svnFilePath, gdFileName, 
repository, "/" + importFolder + "/", user, desc);

其中对获取SVNClientManipulator时,其实是获取一个client客户端,整个过程可以用一个简单的sequence图来说明:

  • 然后对DefaultSVNRepositoryPool内部排查
    在引用到的 DefaultSVNRepositoryPool 的构造方法内,可以看到

这里有两个Timer,作为全局的变量,可以看到作用是从10秒开始,每10秒执行一次 TimeOutTask() 方法。而这里的 myScheduledTimeoutTask 正是 ScheduledFuture 类型的。

如果不确认该类是否为溢出的对象,可以再进入到scheduleWithFixedDelay中。可以看到返回的正是RunnableScheduledFuture 对象。

此时就可以由刚才的seq图往回推

最终问题有可能出现在在 svnCommonService 中创建了过多的 SVNClientManipulator 。

SVN上传代码排查

是否为上述分析的原因,需要在svnCommonService代码中确认。

  • 对现有的SVN上传代码过程分析

svnFileJsonArray 将json解析为array,循环每个数组中的内容,进行文件上传。

首先SVNClientManipulator的创建发生在循环内,也就是说一次请求上传多个文件,则会创建多个客户端。此处为导致问题的主要原因。

其次,在代码内没有发现同步区域,而且大部分内容是需要查询表、插入表,在高并发的情况下可能会出现 mysql 的 lock wait 异常,最终导致文件无法上传的错误。

最后,现有因为代码是一个请求创建一个线程,n次请求就产生n个线程,这在高请求量的情况下出现问题的概率非常大。

基于以上三点进行代码上的修改

问题代码修改

  • 针对SVNClientManipulator的创建发生在循环内

将json中取出一人或者直接传进一个处理人,创建client,减少client的创建次数

  • 针对一次请求创建一个线程,修改为使用线程池,默认线程池的coreSize为8,核心数*2
  • 针对同步代码块,使用sycronized进行同步代码块。

进阶修改:

上述修改后,可以满足过程,但是实际上大部分代码会使用sycronized进行包裹,同时进行的线程只有一个,所以提前创建多线程是占用内存的时间的。

最终修改为:

  1. 使用单线程处理请求,单线程处理业务代码,避免了加锁和解锁的消耗
  2. 仅仅在上传文件时,使用线程池进行文件上传

插曲:为什么DefaultSVNRepositoryPool没有回收掉

这里涉及到客户端连接SVN的状态,默认连接状态时 keepAlive 的,但是不需要手动进行连接的关闭。

一个客户端启动一个全局的Timer,这个Timer每10秒检测一下是否可以关闭连接,可以关闭连接的条件是 这个连接在 60 秒内没有再次访问过SVN。

但是,因为task是需要线程进行执行的,当创建非常多的pool时,timer可能取不到CPU时间片来执行task,所以就在一直等待,导致链上的所有对象,虚拟机都无法进行回收,最终导致内存溢出。


其他

一、JVM调优参数

线上分为三台机器,而应用占其中一台,总内存为16g,针对此环境修改JAVA_OPTS

JAVA_OPTS="-server -Xms8192m -Xmx8192m 
-XX:PermSize=1024M -XX:MaxPermSize=1024M -Duser.language=zh -Djava.util.Arrays.useLegacyMergeSort=true 
-Djava.awt.headless=true 
-Xloggc:/app/okit/java/gc-%t.log 
-XX:+UseGCLogFileRotation 
-XX:NumberOfGCLogFiles=5 
-XX:GCLogFileSize=50M -XX:+PrintGCTimeStamps 
-XX:+PrintGCDetails 
-XX:HeapDumpPath=./java_dump.hprof 
-XX:+HeapDumpOnOutOfMemoryError"

针对其中参数含义解释:

  • -XX:+UseGCLogFileRotation GCLog文件输出
  • -XX:NumberOfGCLogFiles=5 GCLog文件数量
  • -XX:GCLogFileSize=20M GCLog文件大小
  • -XX:+PrintGCTimeStamps 打印GC耗时
  • -XX:+PrintGCDetails 打印GC回收的细节
  • -XX:HeapDumpPath=./java_pid.hprof :堆内存快照的存储文件路径。文件名一般为java__heapDump.hprof。
  • -XX:+HeapDumpOnOutOfMemoryError 在OOM时,自动输出一个dump文件

二、JDK自带工具使用

  • jps

JPS(Java Virtual Machine Process Status Tool),可以显示进行中的Java线程。

使用方式:jps [options] [hostid]

  • jstat -gc

jstat(Java Virtual Machine statistics monitoring tool),能够查看JVM的使用情况

使用方式:jstat [ generalOption | outputOptions vmid [ interval [ s|ms ] [ count ] ] ]

如: jstat -gc -h3 31736 1000 10

  • jstack

jstack(Java stack trace)是Java的堆栈分析工具。

两个功能:

  1. 针对活着的进程做本地的或远程的线程dump;
  2. 针对core文件做线程dump。

使用方式:jstack [ option ] pid

可将堆栈输出到指定文件中:jstack -l PID >> jstack.out

  • jmap

jmap(Java memory map),它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

jmap -dump:format=b,file=heapdump.hprof pid

  • JVisualVM

用来监测JVM内存和线程使用情况,可以远程连接

三、其他分析工具

  • MAT

用来分析堆转储信息,能够分析内存溢出问题

  • Arthas

可以实现JDK中工具所有功能,更直观。还能够线上热部署。

相关推荐

Python操作Word文档神器:python-docx库从入门到精通

Python操作Word文档神器:python-docx库从入门到精通动动小手,点击关注...

Python 函数调用从入门到精通:超详细定义解析与实战指南 附案例

一、函数基础:定义与调用的核心逻辑定义:函数是将重复或相关的代码块封装成可复用的单元,通过函数名和参数实现特定功能。它是Python模块化编程的基础,能提高代码复用性和可读性。定义语法:...

等这么长时间Python背记手册终于来了,入门到精通(视频400集)

本文毫无套路!真诚分享!前言:无论是学习任何一门语言,基础知识一定要扎实,基础功非常的重要,找一个有丰富编程经验的老师或者师兄带着你会少走很多弯路,你的进步速度也会快很多,无论我们学习的目的是什么,...

图解Python编程:从入门到精通系列教程(附全套速查表)

引言本系列教程展开讲解Python编程语言,Python是一门开源免费、通用型的脚本编程语言,它上手简单,功能强大,它也是互联网最热门的编程语言之一。Python生态丰富,库(模块)极其丰富,这使...

Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够

本书是Python经典实例解析,采用基于实例的方法编写,每个实例都会解决具体的问题和难题。主要内容有:数字、字符串和元组,语句与语法,函数定义,列表、集、字典,用户输入和输出等内置数据结构,类和对象,...

Python函数全解析:从入门到精通,一文搞定!

1.为什么要用函数?函数的作用:封装代码,提高复用性,减少重复,提高可读性。...

Python中的单例模式:从入门到精通

Python中的单例模式:从入门到精通引言单例模式是一种常用的软件设计模式,它保证了一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁创建和销毁的对象,比如日志对象、线程池、缓存等...

【Python王者归来】手把手教你,Python从入门到精通!

用800个程序实例、5万行代码手把手教你,Python从入门到精通!...

Python从零基础入门到精通:一个月就够了

如果想从零基础到入门,能够全职学习(自学),那么一个月足够了。...

Python 从入门到精通:一个月就够了

要知道,一个月是一段很长的时间。如果每天坚持用6-7小时来做一件事,你会有意想不到的收获。作为初学者,第一个月的月目标应该是这样的:熟悉基本概念(变量,条件,列表,循环,函数)练习超过30个编...

Python零基础到精通,这8个入门技巧让你少走弯路,7天速通编程!

Python学习就像玩积木,从最基础的块开始,一步步搭建出复杂的作品。我记得刚开始学Python时也是一头雾水,走了不少弯路。现在回头看,其实掌握几个核心概念,就能快速入门这门编程语言。来聊聊怎么用最...

神仙级python入门教程(非常详细),从0到精通,从看这篇开始!

python入门虽然简单,很多新手依然卡在基础安装阶段,大部分教程对一些基础内容都是一带而过,好多新手朋友,对一些基础知识常常一知半解,需要在网上查询很久。...

Python类从入门到精通,一篇就够!

一、Python类是什么?大家在生活中应该都见过汽车吧,每一辆真实存在、能在路上跑的汽车,都可以看作是一个“对象”。那这些汽车是怎么生产出来的呢?其实,在生产之前,汽车公司都会先设计一个详细的蓝图...

学习Python从入门到精通:30天足够了,这才是python基础的天花板

当年2w买的全套python教程用不着了,现在送给有缘人,不要钱,一个月教你从入门到精通1、本套视频共487集,本套视频共分4季...

30天Python 入门到精通(3天学会python)

以下是一个为期30天的Python入门到精通学习课程,专为零基础新手设计。课程从基础语法开始,逐步深入到面向对象编程、数据处理,最后实现运行简单的大语言模型(如基于HuggingFace...

取消回复欢迎 发表评论: