Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >『JVM』我不想知道我是怎么来滴,我就想知道我是怎么没滴

『JVM』我不想知道我是怎么来滴,我就想知道我是怎么没滴

作者头像
古时的风筝
发布于 2020-11-11 08:10:07
发布于 2020-11-11 08:10:07
67000
代码可运行
举报
文章被收录于专栏:古时的风筝古时的风筝
运行总次数:0
代码可运行

我们都知道 Java 程序都是跑在 JVM 上的,一旦 JVM 有什么风吹草动,必然会影响服务的稳定性。幸运的话,服务会发生抖动,可能有部分请求出现延迟或异常。不幸的话,JVM 直接崩溃,导致服务完全中断。

这可不是什么好事,与 JVM 一起崩溃的,除了服务,还有我们的心态。

所谓的 JVM 崩溃,一般情况下就是指内存溢出,也就是 OutOfMemoryError 和 StackOverflowError。另外还有一种情况就是堆外内存占用过大,这种情况会导致 JVM 所在机器的内存被撑爆,从而导致机器重启等异常情况发生,我们把这种情况叫做内存泄漏。

那什么情况下会造成 JVM 崩溃呢,有哪几种类型的崩溃呢?俗话说,知己知彼,方能百战不殆。了解了发生崩溃的原因,才能更好的解决 JVM 崩溃问题。

首先还是放出 JVM 内存模型图,JVM 要理解起来是很抽象的,借助下面这张图可以具象化的了解 JVM 内存模型,而发生溢出的几个部分都可以在图中找到。在 JDK 8 中,永久代已经不存在了,取而代之的是元空间(metaspace)。

下面就以 Hotspot JDK 8 为背景,看一下 JVM 内存溢出和内存泄漏的几种情况。

首先设置 JVM 启动参数,限制堆空间大小,堆空间设置为 20M,其中新生代10M,元空间10M,并指定垃圾收集算法采用 CMS 算法。之后的例子都会使用这套参数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
-XX:+CMSClassUnloadingEnabled
-XX:+ParallelRefProcEnabled
-XX:+CMSScavengeBeforeRemark
-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:+HeapDumpOnOutOfMemoryError
-XX:MetaspaceSize=10M
-XX:MaxMetaspaceSize=10M
-XX:HeapDumpPath=/Users/fengzheng/jvmlog

堆溢出

堆溢出,应该是最常见的一种内存溢出的场景了。JVM 中分配绝大多数对象实例和数组都存在堆上,另外堆内存也是垃圾收集器工作的主要战场。

当我们的 Java 程序启动的时候,会指定堆空间的大小,新建对象和数组的时候会分配到堆上面,当新对象申请空间的时候,如果堆内存不够了,就会发生垃圾收集动作,大多数时候会发生在新生代,叫做 Minor GC。当新生代回收完成,空间仍然不够的话,会发生一次 FullGC。FullGC 后,空间仍然不够,此时就会发生 OOM 错误,也就是堆溢出。

模拟一下这个场景

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private final static int _1K = 1024;

public static void main(String[] args){
  List<byte[]> byteList = new ArrayList<>();
  quietlyWaitingForCrashHeap(byteList);
}

public static void quietlyWaitingForCrashHeap(List<byte[]> byteList) {
  try {
    while (true) {
      byteList.add(new byte[500 * _1K]);
      //Thread.sleep(1000);
      Thread.sleep(100);
    }
  } catch (InterruptedException e) {

  }
}

上面的方法会持续的向List<byte[]>数组中每次添加500k的元素,整个堆只有20M,可想而知,程序一运行起来,马上就会将对空间填满,导致后面的元素加不进去,而又回收不掉,从而导致堆内存溢出。

下面是程序运行之后的结果,经过垃圾回收最终还是没有多余的空间,从而发生 java.lang.OutOfMemoryError: Java heap space异常。

发生堆内存溢出的根本原因就是使用中的对象大小超过了堆内存大小。

堆内存空间设置的太小,要根据预估的实际使用堆大小合理的设置堆空间设置。

程序有漏洞导致,某些静态变量持续的增大,例如缓存数据错误的初始化,导致缓存无止境的增加,最终导致堆内存溢出。针对这种情况,恐怕没什么好方法,除了做好测试之外,就是在问题发生后做好日志分析

栈溢出

虚拟机栈是用来存储局部变量表、操作数栈、动态链接、方法出口等信息的,每调用一个 Java 方法就会为此方法在虚拟机栈中生成栈帧。

栈除了包括虚拟机栈之外,还包括本地方法栈,当调用的方法是本地方法(例如 C 语言实现的方法)时,会用到本地方法栈。不过,在 HotSpot 虚拟机中,虚拟机栈和本地方法栈被合二为一了。

模拟栈溢出场景

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args){
  stackOverflow();
}

/**
* stackoverflow
*/
public static void stackOverflow() {
  stackOverflow();
}

在上面的代码中,stackOverflow() 方法的调用是一个无限递归的过程,没有递归出口。前面说了,每调用一个方法就会在虚拟机栈中生成栈帧,无限的递归,必定造成无限的生成栈帧,最后导致栈空间被填满,从而发生溢出。

上面模拟了最常见的一种状况,产生这种状况的原因很可能是由于程序 bug 导致的,一般来说,递归必定会有递归出口,如果由于某些原因导致了程序在执行的过程中无法达到出口条件,那就会造成这种异常。还有就是循环体,循环体的循环次数如果过大,也有可能出现栈溢出。

另外还可能是其他比较不容易出现的原因,比如创建的线程数过多,线程创建要在虚拟机栈中分配空间,如果创建线程过多,可能会出现 OutOfMemoryError异常,但是一般来说,都会用线程池的方法代替手动创建线程的方式,所以,这种情况不容易出现。

元空间溢出

用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据,在 JDK 8 中,已经用 metaSpace 代替了永久代的。默认情况下 metaSpace 的大小是没有限制的,也就是所在服务器的实际内存大小,但是,一般情况下,最好还是设置元空间的大小。

一般在产生大量动态生成类的情景中,可能会出现元空间的内存溢出。

模拟元空间溢出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args){
  List<byte[]> byteList = new ArrayList<>();
  //quietlyWaitingForCrashHeap(byteList);
  // stackOverflow();
  methodAreaOverflow();
}

public static void methodAreaOverflow() {
  int i = 0;
  while (true) {
    Enhancer enhancer = new Enhancer();
    enhancer.setUseCache(false);
    enhancer.setSuperclass(MethodOverflow.class);
    enhancer.setCallback(new MethodInterceptor() {
      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(o, objects);
      }
    });
    enhancer.create();
    System.out.println(++i);
  }
}

通过 CGLIB 的方式动态的创建很多个动态类,这样一来,类信息就会越来越多的存到元空间,从而导致元空间溢出。

例如在使用 Spring、 MyBatis 等技术框架的时候会动态创建 Bean 实例类,另外,Spring AOP 也会产生动态代理类。

堆外内存溢出

大多数情况下,内存都会在 JVM 堆内存中分配,很少情况下需要直接在堆外分配内存空间。使用堆外内存的几个好处是:

  • 在进程间可以共享,减少虚拟机间的复制
  • 对垃圾回收停顿的改善:如果应用某些长期存活并大量存在的对象,经常会出发YGC或者FullGC,可以考虑把这些对象放到堆外。过大的堆会影响Java应用的性能。如果使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
  • 在某些场景下可以提升程序I/O操纵的性能。少去了将数据从堆内内存拷贝到堆外内存的步骤。

通常在需要大量频繁的进行 IO 操作的时候会用到堆外内存,例如 Netty、RocketMQ 等使用到了堆外内存,目的就是为了加快速度。

所以,在出现系统内存占用过大的情况时,排查堆栈无果后,可以看一下堆外内存的使用情况,看看是不是堆外内存溢出了。

总结

事前做好配置

JVM 问题本身就是比较抽象和难以直观发现的,所以在项目上线前除了做好代码逻辑的测试外,还要对 JVM 参数进行合理配置,根据应用程序的体量和特点选择好合适的参数,比如堆栈大小、垃圾收集器种类等等。

另外,垃圾收集日志一定要有保留,还有就是发生内存溢出时要保存 dump 文件。

事中做好监控

在程序上线运行的过程中,做好 JVM 的监控工作,比如用 Spring Admin 这种比较轻量的监控工具,或者大型项目用 Cat、SkyWallking 等这些分布式链路监控系统。

事后做好现场保护和分析

再合理的参数配置和监控平台,也难免不发生异常,这也是很正常的,不出现异常才有问题好吧。在发生异常之后,要及时的保留现场,如果是多实例应用,可以暂时将发生异常的实例做下线处理,然后再进行问题的排查。如果是单实例的服务,那要及时的确认最新的日志和dump已经留存好,确认完成后,再采取错误让服务重启。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-11-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JVM内存管理与垃圾回收机
Java应用程序是运行在JVM上的,得益于JVM的内存管理和垃圾收集机制,开发人员的效率得到了显著提升,也不容易出现内存溢出和泄漏问题。但正是因为开发人员把内存的控制权交给了JVM,一旦出现内存方面的问题,如果不了解JVM的工作原理,将很难排查错误。本文将从理论角度介绍虚拟机的内存管理和垃圾回收机制,算是入门级的文章,希望对大家的日常开发有所助益。 一、内存管理 也许大家都有过这样的经历,在启动时通过-Xmx或者-XX:MaxPermSize这样的参数来显式的设置应用的堆(Heap)和永久代(Permgen
IT架构圈
2018/06/01
6680
深入点理解JVM-JVM内存模型
CPU的主频不可能无限制的增长,要想很多的提升新能,需要多个处理器协同工作, Intel总裁的贝瑞特单膝下跪事件标志着多核时代的到来。
凯哥Java
2022/12/16
2160
深入点理解JVM-JVM内存模型
BAT面试必问题系列:深入详解JVM 内存区域及内存溢出分析
在JVM的管控下,Java程序员不再需要管理内存的分配与释放,这和在C和C++的世界是完全不一样的。所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题。但是,一旦JVM发生这些情况的时候,如果你不清楚JVM内存的内存管理机制是很难定位与解决问题的。
马士兵的朋友圈
2020/09/09
7530
Java程序员必备:常见OOM异常分析
放假这几天,温习了深入理解Java虚拟机的第二章, 整理了JVM发生OOM异常的几种情况,并分析原因以及解决方案,希望对大家有帮助。
捡田螺的小男孩
2020/04/14
1.4K0
面试必问:说一下 Java 虚拟机的内存布局?
我们通常所说的 Java 虚拟机(JVM)的内存布局,一般是指 Java 虚拟机的运行时数据区(Runtime Data Area),也就是当字节码被类加载器加载之后的执行区域划分。当然它通常是 JVM 模块的第一个面试问题,所以,接下来我们一起来看它里面包含了哪些内容。
磊哥
2023/02/16
3250
面试必问:说一下 Java 虚拟机的内存布局?
Java虚拟机--运行时数据区与内存溢出
存放的数据是JVM加载的类信息,常量,静态变量和编译器编译后的代码等,这里要注意的是JDK1.8之后已经将这个方法区删除了,使用元空间,metaspace代替了,理由有如下:
王小明_HIT
2019/08/13
4890
Java虚拟机--运行时数据区与内存溢出
JVM参数详解及OOM
JVM参数 堆的限制 JVM中最大堆大小有三方面限制: 相关操作系统的数据模型(32-bt还是64-bit)限制 系统的可用虚拟内存限制 系统的可用物理内存限制 32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制 堆参数 -Xmx: 指定JVM的最大堆大小,如:-Xmx=2g -Xms: 指定JVM的最小堆大小,如:-Xms=2g,高并发应用,建议和-Xmx一样,防止因为内存收缩/突然增大带来的性能影响。 -Xmn: 指定JVM中NewGeneration的大小,如:-Xmn256m。这个参
用户1263954
2018/01/30
3.4K0
手写jvm中的各种OOM
 前言     大家好,这篇blog不写什么实际技术,就把我从书上学来的,制造JVM各种OOM的方法告诉大家。下回在遇到有人问你Java会内存溢出吗?你可以快速回答他,会!我还会写各种bug,造成JVM出现OOM异常。 知己知彼,JVM的各个区域的特定 要想写出各种OOM,必须知道JVM各个区域的特点,以便针对性的写bug,造成OOM。下面是我看书后总结的JVM各个区域的特点: 区域名称 作用 是否线程私有 是否会 内存溢出 溢出原因 程序计数器 当前线程所执行的字节码的行号的指示器。 每个线程都有独立的程
温安适
2018/05/17
1.6K0
《Java面试题集中营》- JVM 知识
程序计数器:线程私有,是一块较小的内存空间,可以看做是当前线程执行的字节码指示器,也是唯一的没有定义OOM的区块
阿提说说
2024/07/14
1100
《Java面试题集中营》- JVM 知识
JVM实战—11.OOM的原因和模拟以及案例
9.一个超大数据量处理系统的OOM(数据缓存本地 + 重试发送直到Kafka恢复)
东阳马生架构
2025/03/20
1060
JVM知识点精华汇总
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/details/81490570
DannyHoo
2018/09/13
6530
JVM知识点精华汇总
OOM异常的4种可能分析及常见的OOM异常演示
1.JAVA堆溢出 JAVA堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到这些对象之间有路径可以来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆的容量限制之后就会产生OOM异常
oktokeep
2024/10/09
3900
OOM异常的4种可能分析及常见的OOM异常演示
[JVM] Java 虚拟机(Java Virtual Machine)内存模型
如果使用直接指针访问方式,Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中直接存储的就是对象地址,如下图所示:
架构探险之道
2019/07/25
4860
JVM的深入理解
JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
用户1212940
2022/04/13
3590
JVM的深入理解
深入理解jvm和jvm基本调优参数
所谓虚拟机,就是一台虚拟的机器。他是一款软件,用来执行一系列虚拟计算指令,大体上虚拟机可以分为:系统虚拟机和程序虚拟机, 大名鼎鼎的Visual Box、Vmare就属于系统虚拟机,他们完全是对物理计算的仿真,提供了一个可以运行完整操作系统的软件平台。
从大数据到人工智能
2022/09/16
4490
深入理解jvm和jvm基本调优参数
万字精美图文,带你掌握 JVM 内存布局及细节分析
本JVM系列属于本人学习过程当中总结的一些知识点,目的是想让读者更快地掌握JVM相关的知识要点,难免会有所侧重,若想要更加系统更加详细的学习JVM知识,还是需要去阅读专业的书籍和文档。
搜云库技术团队
2020/02/19
6360
JVM 实战 OutOfMemoryError 异常
Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。
没有故事的陈师傅
2021/09/09
3790
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
开篇,先推荐一篇实用的文章《Java EasyExcel导出报表内存溢出全解析》,作者是【bug菌】。
拉丁解牛说技术
2024/11/15
2750
JDK1.8-Java虚拟机运行时数据区域和HotSpot虚拟机的内存模型
官方文档中规定的运行时数据区一共就几块: PC计数器, 虚拟机栈, 本地方法栈, 堆区, 方法区, 运行时常量池. 这里的官方规定是说, 如果你要做一个Java虚拟机的话, 必须要包含这几个区域, 但是这几个区域在你的虚拟机中是用哪块内存实现的, 这由虚拟机制作者决定.
java架构师
2019/04/25
6130
JDK1.8-Java虚拟机运行时数据区域和HotSpot虚拟机的内存模型
看懂这6张图,理解JVM内存布局就没问题了!
本JVM系列属于本人学习过程当中总结的一些知识点,目的是想让读者更快地掌握JVM相关的知识要点,难免会有所侧重,若想要更加系统更加详细的学习JVM知识,还是需要去阅读专业的书籍和文档。
macrozheng
2020/02/19
4940
看懂这6张图,理解JVM内存布局就没问题了!
相关推荐
JVM内存管理与垃圾回收机
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验