我们都知道 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 算法。之后的例子都会使用这套参数。

-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 错误,也就是堆溢出。

模拟一下这个场景

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 虚拟机中,虚拟机栈和本地方法栈被合二为一了。

模拟栈溢出场景

public static void main(String[] args){stackOverflow();
}/**
* stackoverflow
*/
public static void stackOverflow() {stackOverflow();
}

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

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

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

 

元空间溢出

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

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

模拟元空间溢出

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() {@Overridepublic 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已经留存好,确认完成后,再采取错误让服务重启。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

JVM:我就想知道我是怎么没的相关推荐

  1. JVM难学?那是因为你没认真看完这篇文章

    JVM难学?那是因为你没认真看完这篇文章 一:虚拟机内存图解 JAVA程序运行与虚拟机之上,运行时需要内存空间.虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理. 虚拟机管理 ...

  2. 被信任的感觉,我是真的没想到。。

    是这样的,昨天发布了技术人的五层楼这篇文章之后,目前是半价也就是五折.光昨晚一会会续费的高达 40 多位,同时又有了新入坑的球友.我都不知道说啥了,今年白天续费了高达二十多位球友.总之,说实话,我是真 ...

  3. 推荐Flash:《我不想说我是鸡》

    同事发的链接,本来以为又是普通的搞笑风格的Flash,没想到是一首让孩子演唱的,初听可笑,细听可怜,再听可悲的翻唱歌曲: <我不想说我是鸡>

  4. 我想说:工作没那么难找吧

    工作技能决定价钱 前端的工作越来越难找,到底是不是前端领域已经饱和了? 的确,前端的热度比前几年相比明显降低了很多.前两三年,互联网经济异军突起,热钱涌进来.这个阶段,人才是稀缺资源.所以在前几年,凡 ...

  5. 面试时,HR想坑我,没想到我社会经验这么足,竟然.....

    一提到面试,我想很多人都有聊不完的话题,刚好我最近在找工作,碰到了很多面试的坑,今天分享出来,以下只是本人微小的灼见,望大家海涵. 现在科技这么发达,好多人都在网上开始投简历,合适的线下约,以下面试常 ...

  6. 要想孩子写作文没烦恼?建议家长这样做

    说起语文学习,就不得不提作文.作为语文学习中的重中之重,作文写作一直是压在学生和家长身上的一块"心头大石".发现很多孩子在写作文时,往往存在四大问题:写不出.不生动.流水账.太空洞 ...

  7. 建个数据中心就想发展IDC?没那么简单!

    这篇文章不是在泼冷水,更多的是种反思. 1.聊聊政策背景 国内媒体报道喜欢用一个词叫做--「蔚然成风」,形容一件事情逐渐发展盛行,形成一种良好风气.如果用它来形容产业界的一些现象,或许也很贴切. 比如 ...

  8. 爱奇艺,美团打车Java岗面试经历,这些问题我是真没抗住

    前言 对, 就是今年的金九银十,我满怀希望的抓住了这次面试招聘的好机会,也只是没想到这才开始就遇到了不测,接连面试了两个心仪的公司都没过,面试过程可以算是最心酸了的一次经历了,最心酸的两次-详细的过程 ...

  9. 想做电商没产品?四个选品方向,让你进入电商行业

    大家好,随着互联网的发展,越来越多的人想进入电商行业,但是不知道该做什么产品既能赚钱,风险还小.今天我要和大家分享四个选品方向. 第一个选品方向是微商产品,比如面膜等等.这类产品利润比较高,而且竞争压 ...

最新文章

  1. 分享Kali Linux 2016.2第36周镜像虚拟机
  2. torch.squeeze()和unsqueeze()
  3. 组合电路Verilog的几种描述方式
  4. TypeError: atlas.getSpriteFrame is not a function
  5. 《编译原理》实验教学大纲
  6. CanFestival移植准备工作
  7. iOS开发之oc(五)--成员变量(实例变量)
  8. python 打印的异常回溯和代码不对应
  9. win10计算机怎么拨号上网,win10拨号连接怎么创建 win10宽带拨号连接如何设置
  10. Scikit-learn:聚类clustering
  11. Establishing a tunnel via proxy server failed;
  12. EXCEL中的两大函数语法(subtotal ,sumifs)
  13. 八、OpenDaylight应用基础开发(ODL控制器初级开发流程总结)
  14. 【网易邮箱】换绑安全手机(①之前的手机号注销了怎么办 ②网易人工客服在哪)
  15. 机器学习(MATLAB实现)——SVM支持向量机(一)
  16. 盛大数位红 正式进军手机游戏
  17. linux中查看系统进程的四种方法
  18. 【Mongo】.wt文件数据恢复
  19. HIVE HSQL 基本操作命令
  20. Interview Vocabulary Summary

热门文章

  1. linux系统rc路由配置_详解CentOS 6.4 添加永久静态路由所有方法汇总
  2. jstorm 读取mysql_jstorm运维经验转载
  3. transporter上传卡正在交付_Xcode11,Transporter上传卡在——正在验证 APP - 正在通过App Store进行认证...
  4. openvas 配置遇到的问题
  5. 计组之存储系统:5、cache(cache功能、cache工作原理、cache性能分析)
  6. C++设计模式-Facade模式
  7. 64bit centos 如何通过yum安装32bit的程序
  8. searchsploit漏洞查找工具使用指南(exploit-db.com 离线工具)
  9. twisted系列教程十八–异步操作的并行运行
  10. Redis 与 hash (哈希)相关的常用命令