Jvm实战调优

OOM(Out Of Memory) 内存溢出错误

ps:由于Java虚拟机有许多实现,本文主要阐述的是OpenJDK的HotSpot虚拟机,JDK版本是8。


一、首先要明白造成OOM错误的场景有哪几种?

场景一:

Java堆溢出,即JVM的内存区域堆空间不足引起的错误。

  • 报错信息:

    “java.lang.OutOfMemoryError: Java heap space”。

  • 原因:

    这是OOM最常见的一种情况,原因是因为堆空间不足,而造成堆空间不足的原因多种多样,如果你的Jvm参数设置合理,那么一般就需要考虑

    是由于代码中存在大量无法被正常回收的对象,也就是内存泄漏引起的。

  • 解决手段:

    通过工具分析内存快照文件,来定位出造成堆溢出的对象。那么首先需要获取内存快照文件,然后在进行定位分析。

1、使用命令jmap -dump:format=b,file=F:\StudyFiles\jvm\xxxx-20210817.hprof 7708 输出dump文件,其中7708是Jvm进程id, 或者也可以使用工具MAT动态acquire截取。

2、使用java自带的jvisualvm进行分析,或者也可以使用Eclipse Memory Analyzer进行分析。需要做的就是导入文件,然后通过工具查看泄露对象到GC Roots的引用链,根据泄露对象的类型引用链一般能够准确的找到对象创建的位置。那么就找到了内存泄漏的具体位置。然后根据代码的功能和产生OOM的场景去修复问题。

3、另外,还可以阿里的Arthas对服务进行监控,Arthas是一款强大的Java诊断工具,下面是Arthas Dashboard ,其中对Thread CPU Memory一目了然,而且还可以对调用栈进行跟踪,调用链的时长进行分析。

4、除了上述基本的手段,推荐一个在线分析GC的网站 HeapHero , 使用方法很简单,进入网站,先将自己的内存快照打成压缩包,然后上传,即可观察到分析结果,而且分析准确率高达80,并且还会针对gc提出优化建议。是一个不错的网站。

5、如果通过上述手段并没有发现存在内存泄漏的对象,大对象都是符合预期的存在,那么就要考虑JVM的堆参数 (-Xmx最大堆内存 -Xms最小堆内存),同时检查机器内存,是否可以继续上调参数。也可以查看大对象的生命周期是否符合预期,存储结构是否能做优化,从代码设计上进行优化。


场景二:

Java栈溢出,分为虚拟机栈溢出和本地方法栈溢出。

  • 报错信息:

    “java.lang.StackOverflowError”。

  • 原因:

    这是由于栈空间不足导致的报错,原因在于栈内存无法分配满足需求。

    首先明白,Jvm虚拟机栈是和线程的生命周期一致的,用来保存线程中方法调用的信息。

    Java虚拟机栈溢出有两种可能性:

    1、栈分配的时候,空间不够导致StackOverflowError,这种情况一般是由于,方法内部定义了大本地变量,增加了栈帧中本地变量表的长度。

    2、运行时,方法死递归调用,每个方法就是一个栈帧,虚拟机栈不断压栈,最终会导致栈空间不足StackOverflowError。

  • 解决手段:

    一般出现StackOverflowError,会有明确的堆栈信息打印,很容易就可以定位到是哪个栈帧在入栈时,栈空间不足导致溢出。针对这个方法我们再进一步做分析,到底哪一步出了问题。

场景三:

Java堆溢出,由于无限制创建线程,虚拟机栈一直申请创建空间,导致压缩Jvm整体空间,最终导致Jvm空间不足。

  • 报错信息:

    “java.lang.OutOfMemoryError:unable to create native thread”。

  • 原因:

    操作系统分配给每个进程的内存空间有限制,而Jvm中堆有最大内存限制,而一个线程会创建一个Java虚拟机栈,无限制的创建线程,那么最终会导致Jvm内存不足。

  • 示例代码:

/**
* 32操作系统分配每个进程的大小大约是上限2GB,即很快就可以测出OOM。
*/
public class JavaVmStackOOMTest { private void neverStop() {
while (true){
System.out.println("running ");
}
} public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
neverStop();
}
});
thread.start();
}
} public static void main(String[] args) {
JavaVmStackOOMTest javaVmStackOOMTest = new JavaVmStackOOMTest();
javaVmStackOOMTest.stackLeakByThread();
} }
  • 解决手段:

    出现这种问题,首先根据异常栈信息可以找到是具体在一步创建线程失败了。如果是自己的业务代码本身,那么出现这种问题后,就需要排查自身代码是否必要创建大量线程。如果是三方框架的线程创建出了问题,一般三方框架都会有成熟的池化配置,那么就需要考虑是否做了合理化配置,框架类是否是单例模式进行创建线程等。

场景四:

Jvm方法区溢出

  • 报错信息:

    “java.lang.OutOfMemoryError:PermGen space”。

  • 原因:

    Jvm方法区的实现在JDK8中采用了元空间,移除了永久代,并且将常量池移入了堆中。首先需要知道方法区中存放的是类名、访问修饰符、(常量池,JDK8已经放入堆中,如果发生溢出,报错信息会和场景一相同)、字段描述、方法描述等。所以根据存储内容来分析,发生这部分溢出主要原因是因为运行时产生了大量的类需要进行存储,而实际应用中,spring/hibernate等框架都会使用CGLib进行类增加,那么就会产生大量的类。

  • 示例代码:

/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
  • 解决手段:

    出现这种问题,主要还是因为产生了大量的类,一个类需要被卸载回收,条件往往都是很苛刻的。这里提出几个Jvm调优参数来优化元空间的配置。

  • -XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存

    大小。

  • -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集

    进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放

    了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该

    值。

  • -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可

    减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最

    大的元空间剩余容量的百分比

场景五:

直接内存溢出

  • 报错信息:

    “java.lang.OutOfMemoryError...”。

  • 原因:

    直接内存如果没有设置参数默认和Jvm堆内存最大值一致,通常应用程序中使用直接内存的地方最典型的就是NIO的Buffer.下面使用Unsafe::allocateMemory() 申请直接内存进行代码演示

  • 示例代码:

/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
  • 解决手段:

    出现这种问题,往往堆文件没有什么明显异常,排查起来比较困难,但基于经验,在Java应用中,最典型的就是使用了NIO的Buffer,所以检查代码中使用了直接内存或者NIO的地方,往往追踪到原因。

本篇讨论都是基于Java的内存模型进行了理论上的讨论,但实际应用中,Jvm发生的错误往往不尽相同。而且通常都难以定位追踪。发生这种情况的原因一方面是因为 各种应用的硬件配置和应用的用户量及其使用场景不一样,另一方面的原因是 应用的技术架构多种多样,我们基于理论只能从根本上推导出发生错误得大概原因,具体原因往往需要 结合实际场景。同时在定位到原因之后,如果是需要进行Jvm调优,往往需要我们结合 经验去一步一步进行调优解决。

下一篇会基于 实际应用中产生的问题,结合本篇得理论 来探讨发生OOM时候,如何一步一步定位并进行Jvm调优。


Jvm调优理论篇的更多相关文章

  1. jvm系列(七):jvm调优-工具篇

    16年的时候花了一些时间整理了一些关于jvm的介绍文章,到现在回顾起来还是一些还没有补充全面,其中就包括如何利用工具来监控调优前后的性能变化.工具做为图形化界面来展示更能直观的发现问题,另一方面一些耗 ...

  2. JVM调优-工具篇

    原文地址 16年的时候花了一些时间整理了一些关于jvm的介绍文章,到现在回顾起来还是一些还没有补充全面,其中就包括如何利用工具来监控调优前后的性能变化.工具做为图形化界面来展示更能直观的发现问题,另一 ...

  3. jvm系列(四):jvm调优-命令篇

    运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole.大名鼎鼎的VisualVM,IBM的Memory Analyzer ...

  4. jvm系列(六):jvm调优-工具篇

    ## jdk自带的工具### jconsole Jconsole(Java Monitoring and Management Console)是从java5开始,在JDK中自带的java监控和管理控 ...

  5. 技能篇:linux服务性能问题排查及jvm调优思路

    只要业务逻辑代码写正确,处理好业务状态在多线程的并发问题,很少会有调优方面的需求.最多就是在性能监控平台发现某些接口的调用耗时偏高,然后再发现某一SQL或第三方接口执行超时之类的.如果你是负责中间件或 ...

  6. JVM调优篇

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 基础概念 一般JVM调优,重点在于调整JVM堆大小.调整垃圾回收器 jv ...

  7. JVM调优实战

      JVM调优实战 文档修订记录 版本 日期 撰写人 审核人 批准人 变更摘要 & 修订位置                                                   ...

  8. java虚拟机学习-JVM调优总结-调优方法(12)

    JVM调优工具 Jconsole,jProfile,VisualVM Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用.对垃圾回收算法有很详细的跟踪.详细说明参考这里 ...

  9. jvm调优原则

    合理规划jvm性能调优 JVM性能调优涉及到方方面面的取舍,往往是牵一发而动全身,需要全盘考虑各方面的影响.但也有一些基础的理论和原则,理解这些理论并遵循这些原则会让你的性能调优任务将会更加轻松.为了 ...

随机推荐

  1. elk 7.9.3 版本容器化部署

    ELK-V7.9.3 部署 为什么用到ELK? 平时我们需要进行日志分析的时候,可以直接在日志文件中 grep.awk 就可以过滤出自己想要的信息及关键字,但规模较大的场景中,此方法极大的减低了效率, ...

  2. netty系列之:netty中的懒人编码解码器

    目录 简介 netty中的内置编码器 使用codec要注意的问题 netty内置的基本codec base64 bytes compression json marshalling protobuf ...

  3. 网络安全学习阶段性总结:SQL注入|SSRF攻击|OS命令注入|身份验证漏洞|事物逻辑漏洞|目录遍历漏洞

    目录 SQL注入 什么是SQL注入? 掌握SQL注入之前需要了解的知识点 SQL注入情况流程分析 有完整的回显报错(最简单的情况)--检索数据: 在HTTP报文中利用注释---危险操作 检索隐藏数据: ...

  4. DVWA靶场之XSS(Stored)通关

    Low: <?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage ...

  5. Vue-cli UI界面中插件和依赖的区别是什么?

    Vue-cli UI界面中插件和依赖的区别是什么? 先上结论: 插件在命令行中通过 vue add 安装 如: vue add eslint 这个命令将 @vue/eslint 解析为完整的包名 @v ...

  6. .Net Core 踩坑记录--程序独立发布 无法运行

    背景 创建.net Core3.1 的Console程序 点击发布 选择独立部署模式 目标电脑 Win10 x64 未安装任何.Net SDK 现象 发布的程序 点击运行没有反应 或是直接闪退 解决 ...

  7. [题解] P4556 [Vani有约会]雨天的尾巴

    [题解] P4556 [Vani有约会]雨天的尾巴 ·题目大意 给定一棵树,有m次修改操作,每次修改 \(( x\) \(y\) \(z )\) 表示 \((x,y)\) 之间的路径上数值 \(z\) ...

  8. HCNP Routing&Switching之OSPF LSA类型

    前文我们了解了OSPF中的虚连接相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15202348.html:今天我们来聊一聊OSPF数据包中LSA类型相 ...

  9. new Vue({ render: h => h(App), }).$mount('#app')

    这里创建的vue实例没有el属性,而是在实例后面添加了一个$mount('#app')方法. $mount('#app') :手动挂载到id为app的dom中的意思 当Vue实例没有el属性时,则该实 ...

  10. BST B树 B+树

    二叉排序树/二叉搜索树 (BST) 定义 左子树节点值<根节点值<右子树节点值 默认不允许两个节点的关键值相同 进行中序遍历可以得到递增的有序序列 查找效率 取决与树的高度,最好O(log ...