前几天,我发现了这个小问题:该服务器运行了一段时间,然后掉下来了。 然后通过启动脚本重新启动,整个过程重复进行。 这听起来并不那么糟糕,尽管对数据的损失很大,但对业务的重要性并不重要,因此我决定仔细研究一下,找出问题出在哪里。 首先要注意的是,服务器通过了所有的单元测试和大量的集成测试。 它在使用测试数据的所有测试环境中都能很好地运行,那么生产中出了什么问题? 很容易猜到,在生产中,它的负载可能比测试重,或者比设计所允许的负载大,因此它用尽了资源,但是什么资源?在哪里? 这是一个棘手的问题。

为了演示如何研究此问题,首先要做的是编写一些泄漏的示例代码,而我将使用Producer Consumer模式来执行此操作,因为我可以演示它的大问题。

为了演示泄漏的代码1,我需要像往常一样需要一个高度人为的方案,在这种情况下,您可以想象您在一个将股票销售量记录在数据库中的系统上的股票经纪人工作。 订单由一个简单的线程接收并放入队列中。 然后,另一个线程从队列中获取订单,并将其写入数据库。 的
Order POJO非常简单,如下所示:

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; } }

Order POJO是一个简单的Spring应用程序的一部分,该应用程序具有三个关键抽象,当Spring调用它们的start()方法时,它们会创建一个新线程。

其中第一个是OrderFeed 。 它的run()方法创建一个新的虚拟订单并将其放置在队列中。 然后,它会休眠一会儿,然后再创建下一个订单。

public class OrderFeed implements Runnable { private static Random rand = new Random(); private static int id = 0; private final BlockingQueue<Order> orderQueue; public OrderFeed(BlockingQueue<Order> orderQueue) { this.orderQueue = orderQueue; } /** * Called by Spring after loading the context. Start producing orders */ public void start() { Thread thread = new Thread(this, "Order producer"); thread.start(); } /** The main run loop */ @Override public void run() { while (true) { Order order = createOrder(); orderQueue.add(order); sleep(); } } private Order createOrder() { final String[] stocks = { "BLND.L", "DGE.L", "MKS.L", "PSON.L", "RIO.L", "PRU.L", "LSE.L", "WMH.L" }; int next = rand.nextInt(stocks.length); long now = System.currentTimeMillis(); Order order = new Order(++id, stocks[next], next * 100, next * 10, now); return order; } private void sleep() { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
}

第二类是OrderRecord ,它负责从队列中获取订单并将其写入数据库。 问题在于将订单写入数据库要花费的时间要长得多。 我的recordOrder(…)方法中有1秒的长时间睡眠,这证明了这一点。

public class OrderRecord implements Runnable { private final BlockingQueue<Order> orderQueue; public OrderRecord(BlockingQueue<Order> orderQueue) { this.orderQueue = orderQueue; } public void start() { Thread thread = new Thread(this, "Order Recorder"); thread.start(); } @Override public void run() { while (true) { try { Order order = orderQueue.take(); recordOrder(order); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Record the order in the database * * This is a dummy method * * @param order *            The order * @throws InterruptedException */ public void recordOrder(Order order) throws InterruptedException { TimeUnit.SECONDS.sleep(1); } }

结果很明显: OrderRecord线程无法跟上,队列将越来越长,直到JVM用完堆空间并OrderRecord为止。 这是生产者-消费者模式的最大问题:消费者必须能够跟上生产者的步伐。

为了证明他的观点,我添加了第三类OrderMonitor ,该类每隔几秒钟打印一次队列大小,以便您可以看到出现问题的地方。

public class OrderQueueMonitor implements Runnable { private final BlockingQueue<Order> orderQueue; public OrderQueueMonitor(BlockingQueue<Order> orderQueue) { this.orderQueue = orderQueue; } public void start() { Thread thread = new Thread(this, "Order Queue Monitor"); thread.start(); } @Override public void run() { while (true) { try { TimeUnit.SECONDS.sleep(2); int size = orderQueue.size(); System.out.println("Queue size is:" + size); } catch (InterruptedException e) { e.printStackTrace(); } } } }

为了完成阵容,我在下面添加了Spring上下文:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd" default-init-method="start" default-destroy-method="destroy"><bean id="theQueue" class="java.util.concurrent.LinkedBlockingQueue"/><bean id="orderProducer" class="com.captaindebug.producerconsumer.problem.OrderRecord"><constructor-arg ref="theQueue"/></bean><bean id="OrderRecorder" class="com.captaindebug.producerconsumer.problem.OrderFeed"><constructor-arg ref="theQueue"/></bean><bean id="QueueMonitor" class="com.captaindebug.producerconsumer.problem.OrderQueueMonitor"><constructor-arg ref="theQueue"/></bean></beans>

下一步是启动泄漏的示例代码。 您可以通过转到以下目录来执行此操作

/<your-path>/git/captaindebug/producer-consumer/target/classes

…然后键入以下命令:

java -cp /path-to/spring-beans-3.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:. com.captaindebug.producerconsumer.problem.Main

…其中“ path-to ”是您的jar文件的路径

有一两件事,我真的很讨厌关于Java的是,事实上,它是如此难以运行在命令行中的任何程序。 您必须弄清楚什么是类路径,需要设置哪些选项和属性以及什么是主类。 当然,肯定有可能想到一种简单地键入Java programName的方法,并且JVM找出所有内容在哪里,特别是如果我们开始使用约定而不是配置:它有多难?

您还可以通过附加一个简单的jconsole来监视泄漏的应用程序。 如果要远程运行它,则需要在上面的命令行中添加以下选项(选择您自己的端口号):

-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

…如果您查看使用的堆数量,您会发现随着队列变大,堆逐渐增加。

如果一千字节的内存泄漏了,那么您可能永远也找不到它。 如果一千兆字节的内存泄漏,问题将很明显。 因此,目前要做的只是坐下来等待一些内存泄漏,然后再继续进行下一步调查。 下次再说…

1源代码可以在我在GitHub上的Producer Consumer项目中找到 。

参考: 调查内存泄漏第1部分–在Captain Debug的Blog博客上,由JCG合作伙伴 Roger Hughes 编写泄漏代码 。

翻译自: https://www.javacodegeeks.com/2013/12/investigating-memory-leaks-part-1-writing-leaky-code.html

调查内存泄漏第1部分–编写泄漏代码相关推荐

  1. 内存泄漏代码_调查内存泄漏第1部分–编写泄漏代码

    内存泄漏代码 前几天,我发现了这个小问题:该服务器运行了一段时间,然后掉下来了. 然后通过启动脚本重新启动,整个过程重复进行. 听起来并没有什么坏处,因为它虽然对数据造成了重大损失,但对业务的重要性并 ...

  2. 内存泄漏分析_调查内存泄漏第2部分–分析问题

    内存泄漏分析 这个小型系列的第一个博客介绍了如何创建一个非常泄漏的示例应用程序,以便我们可以研究解决服务器应用程序上基于堆的问题的技术. 它展示了Producer-Consumer模式的一个大问题,即 ...

  3. 调查内存泄漏第2部分–分析问题

    这个小型系列的第一个博客介绍了如何创建一个非常泄漏的示例应用程序,以便我们可以研究解决服务器应用程序上基于堆的问题的技术. 它展示了Producer-Consumer模式的一个大问题,即消费者代码必须 ...

  4. Handler内存泄漏问题解决方案(Android,第一行代码,This Handler class should be static or leaks might occur)

    1 问题背景 博主最近在复习<第一行代码>的第10.2.2章节--在子线程中更新UI,书中给出的在UI主线程中用匿名内部类实现Handler的写法如下: private Handler h ...

  5. 如何在Go中编写防弹代码:不会失败的服务器工作流程

    by Tal Kol 通过塔尔科尔 如何在Go中编写防弹代码:不会失败的服务器工作流程 (How to write bulletproof code in Go: a workflow for ser ...

  6. 人生苦短,使用百度云SDK,编写python代码调用接口的车牌识别

    两个想法 调云在线的接口或者使用SDK做开发(配置环境和编译第三方库很麻烦,当然使用python可以避免这些问题) 自己实现车牌识别算法(复杂) 一开始准备使用百度云文字识别C++ SDK来做,发现需 ...

  7. Django模板(编写html代码

    1.模板 用于编写html代码,还可以嵌入模板代码更快更方便的完成页面开发,再通过在视图中渲染模板,将生成最终的html字符串返回给客户端浏览器 模版致力于表达外观,一个视图可以使用任意一个模板,一个 ...

  8. 嵌入式开发板03---看门狗、编写启动代码

    看门狗 一般CPU的设计在CPU启动后看门狗默认是工作的,在S5pV210内部的iROM代码(BL0)中,其实已经关过看门狗了.很多CPU内部是没有BL0的因此都要在启动代码的前段自己写代码关闭看门狗 ...

  9. python使用spark_如何在Python中编写简单代码,并且速度超越Spark?

    全文共3482字,预计学习时长7分钟 如今,大家都在Python工具(pandas和Scikit-learn)的简洁性.Spark和Hadoop的可扩展性以及Kubernetes的操作就绪之间做选择. ...

最新文章

  1. 微信小程序把玩(四)应用生命周期
  2. 在web项目中使用SpringMVC
  3. linux c编程项目实例,Linux c编程实例_例子
  4. log4j.logger java_java – Log4JLogger的根本原因是找不到还是不可用?
  5. 介绍importlib
  6. defunct 进程占用端口_Linux如何查询哪些端口被占用
  7. 转: EclipseIDE开发 for C++
  8. 0x84bb0001 sqlserver_sqlserver 2000 远程连接 服务器的解决方案
  9. spring注解注入IOC
  10. 从RGB到Lab色彩空间的转换
  11. eclipse创建springboot项目的三种方法
  12. c语言追踪机械腿位置,基于传感器的下肢可穿戴机械腿软件系统设计与实现
  13. sql server 复制_SQL Server复制(合并)–复制什么,什么不复制
  14. C++基础::limits
  15. Android SQL删除表、清空表
  16. Oracle密码过期怎么办
  17. 大学高数常微分方程思维导图_思维导图_2016考研数学:高数中六种常见题型归纳_沪江英语...
  18. Ego的MyBatis框架笔记
  19. 如何修改linux其它用户ulimit,Linux系统设置–ulimit
  20. 将 MATLAB 搬到虚拟机

热门文章

  1. 公共计算机课程思政建设实施方案,公共计算机教研组开设课程思政公开课
  2. Spring MVC开发环境搭建
  3. String path = request.getContextPath()和String basePath = request.getScheme()
  4. kali安装python3.7_Debian服务器之安装Python3.7
  5. FAT12中,如何定位大于一个扇区(512B)的文件内容
  6. java查找链表中间元素_如何通过Java单次查找链表的中间元素
  7. spring依赖注入_Spring依赖注入
  8. java 和javafx_Java,JavaFX的流利设计风格文本字段和密码字段
  9. hibernate自定义_如何自定义Hibernate脏检查机制
  10. 如何通过示例使用Java中的Exchanger