『JVM』我不想知道我是怎么来滴,我就想知道我是怎么没滴
我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!
文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面。
我们都知道 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() {
@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已经留存好,确认完成后,再采取错误让服务重启。
这位英俊潇洒的少年,如果觉得还不错的话,给个推荐可好!
公众号「古时的风筝」,Java 开发者,全栈工程师,bug 杀手,擅长解决问题。
一个兼具深度与广度的程序员鼓励师,本打算写诗却写起了代码的田园码农!坚持原创干货输出,你可选择现在就关注我,或者看看历史文章再关注也不迟。长按二维码关注,跟我一起变优秀!
『JVM』我不想知道我是怎么来滴,我就想知道我是怎么没滴的更多相关文章
- [原创] 【2014.12.02更新网盘链接】基于EasySysprep4.1的 Windows 7 x86/x64 『视频』封装
[原创] [2014.12.02更新网盘链接]基于EasySysprep4.1的 Windows 7 x86/x64 『视频』封装 joinlidong 发表于 2014-11-29 14:25:50 ...
- 『jQuery』.html(),.text()和.val()的使用
『jQuery』.html(),.text()和.val()的使用 2013-04-21 10:25 by 我是文东, 8335 阅读, 0 评论, 收藏, 编辑 本节内容主要介绍的是如何使用jQue ...
- 关于『Markdown』:第二弹
关于『Markdown』:第二弹 建议缩放90%食用 道家有云:一生二,二生三,三生万物 为什么我的帖子不是这样 各位打工人们! 自从我学了Markdown以来 发现 Markdown 语法真的要比 ...
- 关于『HTML』:第一弹
关于『HTML』:第一弹 建议缩放90%食用 根据C2024XSC212童鞋的提问, 我准备写一稿关于『HTML』基础的帖 But! 当我看到了C2024XSC130的 "关于『HTML5』 ...
- 『设计』Slithice 分布式架构设计-支持一体式开发,分布式发布
项目原因: 参与过各种 分布式项目,有 Socket,Remoting,WCF,当然还有最常用的可以跨平台的 WebService. 分布式编码的时间浪费: 但是,无一例外的,开发分布式程序的开发遵循 ...
- 『转载』Debussy快速上手(Verdi相似)
『转载』Debussy快速上手(Verdi相似) Debussy 是NOVAS Software, Inc(思源科技)发展的HDL Debug & Analysis tool,这套软体主要不是 ...
- 『Numpy』内存分析_高级切片和内存数据解析
在计算机中,没有任何数据类型是固定的,完全取决于如何看待这片数据的内存区域. 在numpy.ndarray.view中,提供对内存区域不同的切割方式,来完成数据类型的转换,而无须要对数据进行额外的co ...
- 『TensorFlow』滑动平均
滑动平均会为目标变量维护一个影子变量,影子变量不影响原变量的更新维护,但是在测试或者实际预测过程中(非训练时),使用影子变量代替原变量. 1.滑动平均求解对象初始化 ema = tf.train.Ex ...
- 『TensorFlow』批处理类
『教程』Batch Normalization 层介绍 基础知识 下面有莫凡的对于批处理的解释: fc_mean,fc_var = tf.nn.moments( Wx_plus_b, axes=[0] ...
随机推荐
- Python导入模块的几种方法
Python 模块 Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 模块让你能够有逻辑地组织你的 Python 代 ...
- pytest之将多个测试用例放在一个类中,生成唯一临时文件夹
将多个测试用例放在一个类中 简单来说就是将多个测试用例放到类中,通过pytest去管理,这和Testng很像.示例代码如下: """ 将多个测试用例放到一个类中执行 &q ...
- sqli-labs第三关 详解
通过第二关,来到第三关 我们用了前两种方法,都报错,然后自己也不太会别的注入,然后莫名的小知识又增加了.这居然是一个带括号的字符型注入, 这里我们需要闭合前面的括号. $sql=select * fr ...
- matlab中exist 检查变量、脚本、函数、文件夹或类的存在情况
参考: 1.https://ww2.mathworks.cn/help/matlab/ref/exist.html?searchHighlight=exist&s_tid=doc_srchti ...
- Azure内容审查器之羞羞图审查
上一篇 Azure 内容审查器之文本审查我们已经介绍了如果使用Azure进行文字内容的审核.对于社区内容,上传的图片是否含有羞羞内容也是需要过虑的.但是最为一般开发者自己很难实现这种级别的智能识别.但 ...
- 部署MongoDB-4.2.7
二进制部署很简单 创建存放软件目录下载解压 存放数据和日志及配置文件路径需要手工进行创建 mkdir -p /application/tools/ cd /application/tools/ wge ...
- 草率了,又一个Maven打包的问题
经常遇到 Maven 相关的问题,这是之前的文章: 这个 Maven 依赖的问题,你敢说你没遇到过:https://mp.weixin.qq.com/s/SzBbDtyRUrk_7LH8SUbGXQ ...
- nrf528xx bootloader 模块介绍
1. bootloader 的基本功能: 启动应用 几个应用之间切换 初始化外设 nordic nrf52xxx的bootloader主要功能用来做DFU, 可以通过HCI, UART 或BLE通信的 ...
- jquery的实时触发事件(textarea实时获取中文个数)
jquery的实时触发事件(textarea实时获取中文个数) (2014-09-16 11:49:50) 转载▼ 标签: 实时触发事件 中文个数 onpropertychange oninput o ...
- 【换根DP】小奇的仓库
题目背景 小奇采的矿实在太多了,它准备在喵星系建个矿石仓库.令它无语的是,喵星系的货运飞船引擎还停留在上元时代! 题目内容 喵星系有\(n\)个星球,星球以及星球间的航线形成一棵树. 从星球\(a\) ...