JVM的自愈能力
在IT行业,碰到问题的第一个反应通常是——“你重启过没”——而这样做可能会适得其反,本文要讲述的就是这样的一个场景。
接下来要介绍的这个应用,它不仅不需要重启,而且毫不夸张地说,它能够自我治愈:刚开始运行的时候它可能会碰到些挫折,但会渐入佳境。为了能实际地展示出它的自愈能力,我们尽可能简单地重现了这一场景,这个灵感还得归功于五年前heinz Kabutz发表的一篇老文章:
package eu.plumbr.test;
public class HealMe {
private static final int SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.6);
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000; i++) {
allocateMemory(i);
}
}
private static void allocateMemory(int i) {
try {
{
byte[] bytes = new byte[SIZE];
System.out.println(bytes.length);
}
byte[] moreBytes = new byte[SIZE];
System.out.println(moreBytes.length);
System.out.println("I allocated memory successfully " + i);
} catch (OutOfMemoryError e) {
System.out.println("I failed to allocate memory " + i);
}
}
}
上述代码会循环地分配两块内存。每次分配的内存都是堆中总内存的60%。由于在同一个方法内会不停地进行这个内存分配,因此你可能会认为这段代码会 不断地抛出 java.lang.OutOfMemoryError: Java heap space异常,永远无法正常地执行完allocateMemory方法。
我们先来对源代码进行下静态分析,看看这种猜测是否恰当:
- 乍看一下这段程序的话,这确实是无法成功执行的,因为要分配的内存已经超出了JVM的限制。
- 但再仔细分析下的话我们会发现第一次分配是在一个块作用域内完成的,也就是说这个块中定义的变量仅对块内可见。这意味着这些内存在这个代码块执行完成后便可以回收掉了。这段代码一开始应该是可以成功执行的,只是当它再去尝试分配moreBytes的时候才会挂掉。
- 如果再查看下编译后的class文件的话,你会看到如下的字节码:
private static void allocateMemory(int);
Code:
0: getstatic #3 // Field SIZE:I
3: newarray byte
5: astore_1
6: getstatic #4 // Field java/lang/System.outjava/io/PrintStream;
9: aload_1
10: arraylength
11: invokevirtual #5 // Method java/io/PrintStream.printlnI)V
14: getstatic #3 // Field SIZE:I
17: newarray byte
19: astore_1
20: getstatic #4 // Field java/lang/System.outjava/io/PrintStream;
23: aload_1
24: arraylength
25: invokevirtual #5 // Method java/io/PrintStream.printlnI)V
---- cut for brevity ----
从中能够看出,第一个数组是在位置3~5处完成分配的,并存储到了序号为1的本地变量中。随后在位置17处,正要分配另一个数组。不过由于第一个数 组仍被本地变量所引用着,因此第二次分配总会抛出OOM的异常而失败。字节码解释器不会允许GC去回收第一个数组,因为它仍然存在着一个强引用。
从静态代码分析中可看出,由于底层的两个约束,上述的代码是无法成功执行的,而在第一种情况下则是能够运行的。这三点分析里面哪个才是正确的呢?我 们来实际运行下看看结果吧。结果表明,这些结论都是正确的。首先,应用程序的确无法分配内存。但是,经过一段时间之后(在我的Mac OS X上使用Java 8大概是出现在第255次迭代中),内存分配开始能够成功执行了:
java -Xmx2g eu.plumbr.test.HealMe
1145359564
I failed to allocate memory 0
1145359564
I failed to allocate memory 1 … cut for brevity ... I failed to allocate memory 254
1145359564
I failed to allocate memory 255
1145359564
1145359564
I allocated memory successfully 256
1145359564
1145359564
I allocated memory successfully 257
1145359564
1145359564
Self-healing code is a reality! Skynet is near...
为了搞清楚究竟发生了什么,我们得思考一下,在程序运行期间发生了什么变化?显然,Just-In-Time编译开始介入了。如果你还记得的 话,JIT编译是JVM的一个内建机制,它可以优化热点代码。JIT会监控运行的代码,如果发现了一个热点,它会将你的字节码转化成本地代码,同时会执行 一些额外的优化,譬如方法内联以及无用代码擦除。
我们打开下面的命令行参数重启下程序,看看是否触发了JIT编译。
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation
这会生成一个日志文件,在我这里是一个hotspot_pid38139.log文件,38139是Java进程的PID。在该文件中可以找到这么一行:
<task_queued compile_id='94' method='HealMe allocateMemory (I)V' bytes='83' count='256' iicount='256' level='3' stamp='112.305' comment='tiered' hot_count='256'/>
这说明,在运行了256次allocateMemory()方法2之后,C1编译器决定将这个方法进行3级编译。看下这里可以了解下分层编译的各个级别以及不同的阈值。在前面的256次迭代中这段程序都是在解释模式下运行的,这里的字节码解释器就是一个简单堆栈机器,它无法提 前预知某个变量后续是否会被用到,在这里对应的是变量bytes。但是JIT会一次性查看整个方法,因此它能推断出后面不会再用到bytes变量,可以对 它进行GC。所以才会触发垃圾回收,因此我们的程序才能奇迹般地自愈。我只是希望本文的读者都不要在生产环境碰到调试这类问题的情况。不过如果你想让某人 抓狂的话,倒是可以试试在生产环境中加下类似的代码。
JVM的自愈能力的更多相关文章
- JVM 性能优化, Part 4: C4 垃圾回收
ImportNew注:本文是JVM性能优化 系列-第4篇.前3篇文章请参考文章结尾处的JVM优化系列文章.作为Eva Andreasson的JVM性能优化系列的第4篇,本文将对C4垃圾回收器进行介绍. ...
- jvm参数解析(含调优过程)
前阵 对底层账单系统进行了压测调优,调优的最后一步--jvm启动参数中,减小了线程的堆栈空间:-XX:ThreadStackSize=256K,缩减至原来的四分之一,效果明显,不过并没有调 ...
- 深入理解JVM(6)——JVM性能调优实战
如何在高性能服务器上进行JVM调优:以便充分利用高性能服务器的硬件资源,有两种JVM调优方案. 一. 采用64位操作系统,并为JVM分配大内存 分析:如果JVM中堆内存太小,那么就会频繁 ...
- JVM核心知识体系(转http://www.cnblogs.com/wxdlut/p/10670871.html)
1.问题 1.如何理解类文件结构布局? 2.如何应用类加载器的工作原理进行将应用辗转腾挪? 3.热部署与热替换有何区别,如何隔离类冲突? 4.JVM如何管理内存,有何内存淘汰机制? 5.JVM执行引擎 ...
- K8S节点异常怎么办?TKE"节点健康检查和自愈"来帮忙
节点健康检测 意义 在K8S集群运行的过程中,节点常常会因为运行时组件的问题.内核死锁.资源不足等各种各样的原因不可用.Kubelet默认对节点的PIDPressure.MemoryPressure. ...
- 深入理解Java虚拟机之JVM内存布局篇
内存布局**** JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来 ...
- 分布式应用框架Akka快速入门
转自:http://blog.csdn.net/jmppok/article/details/17264495 本文结合网上一些资料,对他们进行整理,摘选和翻译而成,对Akka进行简要的说明.引用资料 ...
- C4 垃圾回收
使用C4垃圾回收器可以有效提升对低延迟有要求的企业级Java应用程序的伸缩性. 到目前为止,stop-the-world式的垃圾回收视为影响Java应用程序伸缩性的一大障碍,而伸缩性又是现代企业级Ja ...
- 国产开源优秀新一代MPP数据库StarRocks入门之旅-数仓新利器(上)
概述 背景 Apache Doris官方地址 https://doris.apache.org/ Apache Doris GitHub源码地址 https://github.com/apache/i ...
随机推荐
- Dawn开源项目
今天本人给大家推荐一个阿里开源的前端构建和工程化工具Dawn. 一. Dawn是什么? Dawn 取「黎明.破晓」之意,原为「阿里云·业务运营团队」内部的前端构建和工程化工具,现已完全开源.它通过 p ...
- Dubbo -- 系统学习 笔记 -- 配置
Dubbo -- 系统学习 笔记 -- 目录 配置 Xml配置 属性配置 注解配置 API配置 配置 Xml配置 配置项说明 :详细配置项,请参见:配置参考手册 API使用说明 : 如果不想使用Spr ...
- 【转】numpy中mean和average的区别
转自:https://blog.csdn.net/Muzi_Water/article/details/85104941 mean和average都是计算均值的函数,在不指定权重的时候average和 ...
- svn 更新提交文件冲突
文件冲突定义:svn up更新服务器文档到本地的时候发现本地的文件有所改动,和svn服务器不同步 服务器会报冲突,让你觉得已谁的为准,根据实际情况我们需要选择是以服务器还是以本地代码为准 报错: Co ...
- 【转】C#中对IDisposable接口的理解
IDisposable接口定义:定义一种释放分配的资源的方法. .NET 平台在内存管理方面提供了GC(Garbage Collection),负责自动释放托管资源和内存回收的工作,但它无法对非托管资 ...
- 多模匹配算法之Aho-Corasick
除剔除那些含有敏感词的文本,由于有大量的敏感词,所以通过简单的正则表达式和字符串查找的方式效率太低,每次都有遍历一次字符串.而AC算法的核心思想就是避免不必要的回溯使搜索一直沿着向前的方向,最大可能的 ...
- 20155207 《网络对抗》exp4 恶意代码分析 学习总结
20155207 <网络对抗> 恶意代码分析 学习总结 实践目标 1.是监控你自己系统的运行状态,看有没有可疑的程序在运行. 2.是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件 ...
- Exp7 网络欺诈技术防范
Exp7 网络欺诈技术防范 基础问题回答 1.通常在什么场景下容易受到DNS spoof攻击? 在同一局域网下比较容易受到DNS spoof攻击,攻击者可以冒充域名服务器,来发送伪造的数据包,从而修改 ...
- 总是套路留人心, JAVA提供的套路: LinkedHashMap实现LRU缓存; InvocationHandler实现动态代理; fork/join实现窃取算法
1. LinkedHashMap实现LRU缓存 LRU缓存核心是根据访问顺序排序, 自动移除队尾缓存, LinkedHashMap已经实现了这些要求: public LRUCache<K, V& ...
- Kubernetes学习之路(十九)之Kubernetes dashboard认证访问
Dashboard:https://github.com/kubernetes/dashboard 一.Dashboard部署 由于需要用到k8s.gcr.io/kubernetes-dashboar ...