探究内存泄露—Part1—编写泄露代码
本文由 ImportNew - 黄索远 翻译自 captaindebug。如需转载本文,请先参见文章末尾处的转载要求。
ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Java开发 小组。参与方式请查看小组简介。
几天前我发现了一个小问题:有一个服务器在跑了一段时间后挂掉了。重启脚本和系统后,这个问题还是会出现。因为问题代码不是关键业务,所以尽管有大量的数据丢失,但是问题并不严重。不过我还是决定作进一步的调查,来探寻一下问题到底出现在哪。首先注意到的是,服务器通过了所有的单元测试和集成环境的完整测试。在测试环境下使用测试数据时运行得非常正常。那么为什么在工作环境中一跑起来就会出现问题呢?很容易就能想到,也许是因为在实际运行时的负载大于测试,甚至超过了设计时所能承载的负重,从而耗尽了资源。但是到底是什么资源,又是在哪里耗尽的呢?这就是本文需要探究的难题。
为了演示如何调查这个问题,第一件事情就是写一些内存泄露的代码。我将会采用生产者—消费者模型,以便更好的说明这个问题。
和往常一样,为了说明内存泄露代码,我需要人为建立一个场景。在这个场景中,假定你为一个证劵经纪公司工作,这个公司将股票的销售额和股份记录在一个数据库中。通过一个简单进程获取命令并将其存放在一个队列中。另一个进程从该队列中读取命令并将其写入数据库。命令的POJO(简单Java对象)非常的直观:
public class Order {
private final int id;
private final String code;
private final int amount;
private final double price;
private final long time;
private final long[] padding;
/**
* @param id
* The order id
* @param code
* The stock code
* @param amount
* the number of shares
* @param price
* the price of the share
* @param time
* the transaction time
*/
public Order(int id, String code, int amount, double price, long time) {
super();
this.id = id;
this.code = code;
this.amount = amount;
this.price = price;
this.time = time;
// This just makes the Order object bigger so that
// the example runs out of heap more quickly.
this.padding = new long[3000];
Arrays.fill(padding, 0, padding.length - 1, -2);
}
public int getId() {
return id;
}
public String getCode() {
return code;
}
public int getAmount() {
return amount;
}
public double getPrice() {
return price;
}
public long getTime() {
return time;
}
}
这个命令POJO是Spring应用的一部分。这个应用有三个主要的抽象类,当应用调用他们的start()方法时分别创建一个新进程。
第一个抽象类是OrderFeed。run()方法会生成一个虚拟的命令并将其放置在队列中。生成命令后它会睡眠一会儿,然后生成一个新的命令。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
publicclassOrderFeedimplementsRunnableprivatestaticRandomnewRandom();privatestaticintid0;privatefinalBlockingQueue<Order>publicOrderFeed(BlockingQueue<Order>this.orderQueue}/****/publicvoidstart()ThreadnewThread(this,"Order);thread.start();}/**@Overridepublicvoidrun()while(true)OrderorderQueue.add(order);sleep();}}privateOrderfinalString[]"BLND.L","DGE.L","MKS.L","PSON.L","RIO.L","PRU.L","LSE.L","WMH.L"};intnextlongnowOrdernewOrder(++id,100,10,returnorder;}privatevoidsleep()try{TimeUnit.MILLISECONDS.sleep(100);}catch(InterruptedExceptione.printStackTrace();}}} |
第二个类是OrderRecord,这个类负责从队列中提取命令并将它们写入数据库。问题是,将命令写入数据库的耗时比产生命令的耗时要长得多。为展示这一现象,我将在recordOrder()方法中让其睡眠1秒。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
publicclassOrderRecordimplementsRunnableprivatefinalBlockingQueue<Order>publicOrderRecord(BlockingQueue<Order>this.orderQueue}publicvoidstart()ThreadnewThread(this,"Order);thread.start();}@Overridepublicvoidrun()while(true)try{OrderrecordOrder(order);}catch(InterruptedExceptione.printStackTrace();}}}/******** **/publicvoidrecordOrder(OrderthrowsInterruptedExceptionTimeUnit.SECONDS.sleep(1);} |
结果将是显而易见的,OrderRecord线程跟不上命令产生的速度,导致这个队列越来越长,直到JAVA虚拟机用尽堆内存从而崩溃。这就是生产者—消费者模式的存在一个大问题:消费者的速度必须跟上生产者的速度。
为了证明这一点,我加入了第三个类OrderMonitor。这个类每隔几秒就会打印出队列的大小,这样就能看到运行时产生的问题。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
publicclassOrderQueueMonitorimplementsRunnable privatefinalBlockingQueue<Order> publicOrderQueueMonitor(BlockingQueue<Order> this.orderQueue } publicvoidstart() ThreadnewThread(this,"Order); thread.start(); } @Overridepublicvoidrun() while(true) try{ TimeUnit.SECONDS.sleep(2); intsize System.out.println("Queue }catch(InterruptedException e.printStackTrace(); } }} |
为了完成Spring框架,我加入了应用上下文,示例代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:p="http://www.springframework.org/schema/p"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/contextdefault-init-method="start"default-destroy-method="destroy"><beanid="theQueue"/><beanid="orderProducer"><constructor-argref="theQueue"/></bean><beanid="OrderRecorder"><constructor-argref="theQueue"/></bean><beanid="QueueMonitor"><constructor-argref="theQueue"/></bean></beans> |
下一步就是把这个内存泄露的代码跑起来,你需要改变下面的目录:
|
1
|
<your-path>/git/captaindebug/producer-consumer/target/classes |
然后输入下面的命令:
|
1
|
java3.2.3.RELEASE.jar:/path-to/spring-context-3.2.3.RELEASE.jar:/path-to/spring-core-3.2.3.RELEASE.jar:/path-to/slf4j-api-1.6.1-javadoc.jar:/path-to/commons-logging-1.1.1.jar:/path-to/spring-expression-3.2.3.RELEASE.jar:. |
“path-to”对应着你的jar文件目录。
Java比较讨厌的一点是,从命令行来运行程序非常的困难——你必须要搞清楚类的目录、选项、需要设定的属性、main所在的类在哪里。当然,有方法能让你只需要输入Java的项目名称,然后Java虚拟机帮你把一切都搞定,特别是使用默认设置:这有多难呢?
你也可以通过附加一个简单的JConsole来监控应用程序的内存泄漏。如果你最近运行过,则需要在上面的命令行中添加如下的选项(选择自己的端口号):
|
1
2
3
4
5
|
-Dcom.sun.management.jmxremote-Dcom.sun.management.jmxremote.port=9010-Dcom.sun.management.jmxremote.local.only=false-Dcom.sun.management.jmxremote.authenticate=false-Dcom.sun.management.jmxremote.ssl=false |
如果你看看堆的使用量,你会发现随着队列的增大堆逐渐变大。

你可能不会发现1KB的内存泄露,但1GB的内存泄露就很明显了。所以,接下来要做的事情就是等待内存的泄露直到进入下一个阶段的研究。下回见……
原文链接: captaindebug 翻译: ImportNew.com- 黄索远
译文链接: http://www.importnew.com/7807.html
探究内存泄露—Part1—编写泄露代码的更多相关文章
- Linux kernel 内存泄露本地信息泄露漏洞
漏洞名称: Linux kernel 内存泄露本地信息泄露漏洞 CNNVD编号: CNNVD-201311-467 发布时间: 2013-12-06 更新时间: 2013-12-06 危害等级: ...
- 对开发中常见的内存泄露,GDI泄露进行检测
对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!【转载+整理】
原文地址 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和for-of循环 生成器 Generators 模板字符串 不定参数和默认参数 解构 Destructu ...
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!
原文地址 迁移到:http://www.bdata-cap.com/newsinfo/1741515.html 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和f ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
- [JS进阶] 编写可维护性代码 (1)
今天的web应用大至成千上万行的javascript代码,执行各种复杂的过程,这种演化让我们开发者必须得对可维护性有一定的认识!编写可维护性代码很重要,很多情况下我们是以他人的工作成果为基础,确保代码 ...
- WEB开发:如何用js来模拟服务器的ajax响应,不依赖服务器来编写前端代码
一.问题的提出 目前web前端开发,主流的思路是: 1)编写静态的html文件(不使用模板技术,与服务器无关) 2)页面通过ajax与服务器交互,进行数据的传输,数据格式为json格式 这里存在一个问 ...
- 使用 Golang 编写链代码 (v0.6 )
https://www.ibm.com/developerworks/cn/cloud/library/cl-ibm-blockchain-chaincode-testing-using-golang ...
- C# 中编写函数式代码
http://www.dotnetcurry.com/csharp/1384/functional-programming-fsharp-for-csharp-developers 写给 C# 开发人 ...
- 通过编写Java代码让Jvm崩溃
在书上看到一个作者提出一个问题"怎样通过编写Java代码让Jvm崩溃",我看了之后也不懂.带着问题查了一下,百度知道里面有这样一个答案: 1 package jvm; 2 3 pu ...
随机推荐
- [postgres]使用pgbench进行基准测试
前言 pgbench是一种在postgres上进行基准测试的简单程序,一般安装后就会自带.pgbench可以再并发的数据库绘画中一遍遍地进行相同序列的SQL语句,并且计算平均事务率. 测试准备 既然要 ...
- [python][selenium] Web UI自动化页面切换iframe框架
关联文章:Web UI自动化8种页面元素定位方式 1.切换iframe的方法:switch_to.frame 入参有4种: 1.1.id 1.2.name 1.3.index索引 1.4.i ...
- RxJS 系列 – Utility Operators
前言 前几篇介绍过了 Creation Operators Filtering Operators Join Creation Operators Error Handling Operators T ...
- SQL Management studio copy paste result out (string_agg line break)
refer : https://stackoverflow.com/questions/59283754/string-agg-with-line-break string agg char(10) ...
- Clickhouse-insert 数据写入不成功问题
[应用场景] 对副本表进行 alter delete 数据后,同样的数据再进行 insert into 操作. [问题复现] [问题解释] 对副本表 insert 语句的数据会划分为数据块. 每个数据 ...
- SqlEs-像使用数据库一样使用Elasticsearch
SqlEs SqlEs是Elasticsearch的客户端JDBC驱动程序,支持采用sql语法操作Elasticsearch.SqlEs构建在RestHighLevelClient,屏蔽了RestHi ...
- Kubernetes基础(kube-apiserver?kube-controller-manager?kube-scheduler?kubelet?kube-proxy?kubectl?)(十一)
一.kube-apiserver API Server 提供了资源对象的唯一操作入口,其它所有组件都必须通过它提供的 API 来操作资源数据.只有 API Server 会与 etcd 进行通信,其它 ...
- Android Qcom USB Driver学习(八)
因为要看usb charging的问题,所以需要补充一下battery的相关知识,算是入门吧 BAT SCH (1)VBATT_VSNS_P (2)BAT_THERM (3)I2C_SDA (4)I2 ...
- iOS中修饰符常用小结
1.copy,是复制引用对象地址的深拷贝 a:当修饰不可变类型的属性时,如NSArray.NSDictionary.NSString,用copy,用copy为关键字的话,调用setter方法后.是对赋 ...
- CSharp的Where底层实现
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using Syst ...