JVM之调优和GC日志分析

news/2024/10/30 22:22:08/

GC日志

GC日志参数

  • -verbose:gc 输出gc日志信息,默认输出到标准输出
  • -XX:+PrintGC 输出GC日志,类似:verbose:gc
  • -XX:+PrintGCDetails 在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况
  • -XX:+PrintGCTimeStamps 输出GC发生时的时间戳
  • -XX:+PrintGCDateStamps 输出GC发生的时间戳(以日期的形式)
  • -XX:+PrintHeapAtGC 每一次GC前和GC后,都打印堆信息
  • -Xloggc<file> 表示把GC日志写入到一个文件中去,而不是打印到标准输出中

GCeasy网站可以在线分析GC日志文件

JVM调优

jmap命令

dump命令

jmap -dump:live,format=b,file=heap.bin <pid>

  • live 仅打印存活的对象
  • all 打印所有的对象
  • format=b 二进制格式
  • gz=<number> 指定是否压缩以及压缩程度,堆转储将使用给定的压缩级别以gzip格式写入,1最快,9压缩最大
  • file=<file> 转储到哪个文件

OOM案例

OOM问题的排除和解决没有一个放之四海而皆准的银弹,只能是具体的情况根据监控和技术手段去分析(甚至需要深入的了解业务在某些特殊情况下),不过大体的思路还是可以借鉴的

堆溢出

  • 原因
  • 代码中可能存在大对象分配
  • 可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
  • 解决方法
  • 检查是否存在大对象的分配,最有可能的是大数组分配
  • 通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题
  • 如果没有找到明显的内存泄漏,使用 -Xmx 加大堆内存
  • 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

元空间溢出

JDK8后,元空间替换了永久代,元空间使用的是本地内存

  • 原因:
  • 运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
  • 应用长时间运行,没有重启
  • 元空间内存设置过小
  • 因为该 OOM 原因比较简单,解决方法有如下几种:
  • 检查是否永久代空间或者元空间设置的过小
  • 检查代码中是否存在大量的反射操作
  • dump之后通过mat检查是否存在大量由于反射生成的代理类

GC overhead limit exceeded

这个是JDK6新加的错误类型,一般都是堆太小导致的。Sun 官方对此的定义: 超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出
解决方法:

  • 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码.
  • 添加参数-XX:-UseGCOverheadLimit’ 禁用这个检查,其实这个参数解决不了内存题,只是把错误的信息延后,最终出现 java.lang.OutofMemoryError: Java heapspace。
  • dump内存,检查是否存在内存泄漏,如果没有,加大内存。

线程溢出

解决方向1
  • 通过 -Xss 设置每个线程栈大小的容量
  • JDK5.以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
  • 正常情况下,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000到5000左右。
  • 能创建的线程数的具体计算公式如下:(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Numberof threads
  • MaxProcessMemory 指的是进程可寻址的最大空间
  • JVMMemory JVM内存
  • ReservedOsMemory 保留的操作系统内存
  • ThreadStackSize 线程栈的大小

在Java语言里,当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象,同时创建一个操作系统线程,而这个操作系统线程的内存用的不是JVMMemory,而是系统中剩下的内存,(MaxProcessMemory - JVMMemory - ReservedOsMemory).所以得出结论.你给JVM的内存越多,那么你能创建的内存越少,越容易发生这个问题

解决方向2

线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

  • /proc/sys/kernel/pid_max 系统最大pid值,在大型系统里可适当调大
  • /proc/sys/kernel/threads-max 系统允许的最大线程数
  • maxuserprocess (ulimit -u) 系统限制某用户下最多可以运行多少进程或线程
  • /proc/sys/vm/max_map_count
  • max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建。调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错,因为当进程达到了VMA上线但又只能释放少量的内存给其他的内核进程使用时,操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。

jvm自动的调优

栈上分配对象(标量替换)

参数设置

  • 在JDK 6u23版本之后,HotSpot中默认就已经开启了逃逸分析。
  • 如果使用的是较早的版本,开发人员则可以通过:
  • 通过选项“-XX:+DoEscapeAnalysis”显式开启逃逸分析通过选项
  • “-XX: +PrintEscapeAnalysis”查看逃逸分析的筛选结果。
    结论:开发中能使用局部变量的,就不要使用在方法外定义。

锁消除

多个锁会被JIT并为一个,或者无用锁甚至会被直接消除掉

jvm优化

设置堆的大小

Java整个堆大小设置,Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。方法区 (永久代 PermSize和MaxPermSize 或 元空间 MetaspaceSize 和MaxMetaspaceSize) 设置为老年代存活对象的1.2-1.5倍。年轻代Xmn的设置为老年代存活对象的1-1.5倍老年代的内存大小设置为老年代存活对象的2-3倍。

估算方式1

JVM参数中添加GC目志,GC日志中会记录每次FulIGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天) 的FullGC之后的内存情况,根据多次的FulIGC之后的老年代的空间大小数据来预估FullGC之后老年代的存活对象大小(可根据多次FulIGC之后的内存大小取平均值)。

估算方式2
  • 会影响线上服务,慎用!
  • 方式1的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用CMS回收器的时候,有可能不能触发FulIGC,所以日志中并没有记录FullGC的日志。在分析的时候就比较难处理。 所以,有时候需要强制触发一次FullGC,来观察FullGC之后的老年代存活对象大小。
  • 注: 强制触发FullGC,会造成线上服务停顿(STW),要谨慎!建议的操作方式为,在强制FullGC前先把服务节点摘除,FullGC之后再将服务挂回可用节点,对外提供服务,在不同时间段触发FullGC,根据多次FullGC之后的老年代内存情况来预估FullGC之后的老年代存活对象大小
  • 如何强制触发Full GC?
  • jmap -dump:live,format=b,file=heap.bin <pid> 将当前的存活对象dump到文件,此时会触发FulIGC
  • jmap -histo:live <pid> 打印每个class的实例数目,内存占用,类全名信息.live子参数加上后,只统计活的对象数量。此时会触发FulIGC
  • 在性能测试环境,可以通过Java监控工具来触发FullGC,比如使用VisualVM和JConsole.VisualVM集成了JConsole,VisualVM或者]Console上面有一个触发GC的按钮。

估算youngGC的频率

比如从数据库获取一条数据占用128个字节,需要获取1000条数据,那么一次读取到内存的大小就是128 B/1024 Kb/1024M) * 1000 = 0.122M ,那么我们程序可能需要并发读取,比如每秒读取100次,那么内存占用就是0.122100 = 12.2M ,如果堆内存设置1个G,那么年轻代大小大约就是333M,那么333M80% /12.2M =21.84s ,也就是说我们的程序几乎每分钟进行两到三次youngGC。这样可以让我们对系统有一个大致的估算。(实际上是算不出来的,因为请求太多太复杂,还是要看实时的日志还有监控来确定)

CPU占用飙高调优

排查流程

  • ps aux| grep java 查看到当前java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
  • top -Hp 进程pid 检查当前使用异常线程的pid
  • 把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
  • jstack 进程的pid| grep -A20 0x7bcf 得到相关进程的代码

日均百万订单优化JVM

堆稍微分配大一些,适当的提高年轻代的比例(防止对象过多全进入到老年代),然后水平扩展更多的机器,走负载均衡.

ARTHAS

在线的Java调优分析工具,以及bug的查找,非常之好用
官方网址传送门


http://www.ppmy.cn/news/107875.html

相关文章

Redis7实战加面试题-高阶篇(布隆过滤器BloomFilter,缓存预热+缓存雪崩+缓存击穿+缓存穿透)

布隆过滤器BloomFilter 先看看大厂真实需求面试题反馈 1.现有50亿个电话号码&#xff0c;现有10万个电话号码&#xff0c;如何要快速准确的判断这些电话号码是否已经存在? 2.判断是否存在&#xff0c;布隆过滤器了解过吗? 3.安全连接网址&#xff0c;全球数10亿的网址判断 …

WAV 格式和音频裁剪、转码处理

文章目录 0、参考资料1、WAV 格式了解1.1 WAV 文件头1.2 RIFF Chunk 区块1.3 Format Chunk 区块1.4 Data Chunk 区块 2、音频剪裁 -> 解码 -> 编码2.1 mp32.1.1 裁剪2.1.2 解码2.1.3 编码 2.2 pcm 裁剪 0、参考资料 【音频处理】WAV 文件格式分析 ( 逐个字节解析文件头 …

自学网络安全有什么好方法?

一、网络安全学习的误区 1.不要试图以编程为基础去学习网络安全 不要以编程为基础再开始学习网络安全&#xff0c;一般来说&#xff0c;学习编程不但学习周期长&#xff0c;且过渡到网络安全用到编程的用到的编程的关键点不多。一般人如果想要把编程学好再开始学习网络安全往…

蓝库云:让销售人员搭配客服工单系统,已成销售企业必备的组合

让销售人员搭配客服工单系统&#xff0c;已成了众多销售企业必备的组合&#xff0c;这不但可以大大提高客户满意度和转化率&#xff0c;还有效跟踪客户及时收到客户的反馈&#xff0c;从而进一步优化产品及策略。站在企业的角度来说企业也可以可以进行数据分析和优化&#xff0…

【创造一个源点去建图】【有等级限制的dijkstra(采用多次dijk方法处理)】昂贵的聘礼

昂贵的聘礼 题意分析 原题链接 题意分析 本题需要注意&#xff1a; 等级限制比较复杂&#xff0c;可以最后考虑本题说 由 B物品 可以换 A物品&#xff0c;想到了B节点可以走到A节点&#xff0c;所以构建图由于我们是要买一个点再开始换的&#xff0c;所以我们可以构建一个源点…

毕业5年的同学突然告诉我,他已经是年薪30W的自动化测试工程师,我愣住了...

作为一名程序员&#xff0c;都会对自己未来的职业发展而焦虑。一方面是因为IT作为知识密集型的行业&#xff0c;知识体系复杂且知识更新速度非常快&#xff0c;“一日不学就会落后”。 另外一方面&#xff0c;IT又是劳动密集型的行业&#xff0c;不仅业人员多&#xff0c;而且个…

在pycharm里安装pytorch环境-GPU版

1、安装Anaconda 在官网下载安装&#xff1a;https://www.anaconda.com/download 2、安装pycharm https://www.jetbrains.com/pycharm/download/#sectionwindows 使用社区版即可。 3、检查conda环境 按winr&#xff0c;输入cmd回车打开命令窗 在命令窗内输入conda 环境无问…

解锁接口关联测试新技能!HttpRunner教你如何轻松搞定。

目录 前言&#xff1a; 一、安装HttpRunner 二、编写测试用例 三、运行测试用例 四、实现接口关联测试 五、总结 前言&#xff1a; 在接口自动化测试中&#xff0c;一个常见的场景就是需要对多个接口进行关联测试&#xff0c;例如登录后获取token&#xff0c;再利用token…