调查内存泄漏第1部分–编写泄漏代码
前幾天,我發現了這個小問題:該服務器運行了一段時間,然后掉下來了。 然后通過啟動腳本重新啟動,整個過程重復進行。 這聽起來并不那么糟糕,盡管對數據的損失很大,但對業務的重要性并不重要,因此我決定仔細研究一下,找出問題出在哪里。 首先要注意的是,服務器通過了所有的單元測試和大量的集成測試。 它在使用測試數據的所有測試環境中都能很好地運行,那么生產中出了什么問題? 很容易猜到,在生產中,它的負載可能比測試重,或者比設計所允許的負載大,因此它用盡了資源,但是什么資源?在哪里? 這是一個棘手的問題。
為了演示如何研究此問題,首先要做的是編寫一些泄漏的示例代碼,而我將使用Producer Consumer模式來執行此操作,因為我可以演示它的大問題。
為了演示泄漏的代碼1,我需要像往常一樣需要一個高度人為的方案,在這種情況下,您可以想象您在一個將股票銷售量記錄在數據庫中的系統上的股票經紀人工作。 訂單由一個簡單的線程接收并放入隊列中。 然后,另一個線程從隊列中獲取訂單,并將其寫入數據庫。 的
Order POJO非常簡單,如下所示:
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項目中找到 。
翻譯自: https://www.javacodegeeks.com/2013/12/investigating-memory-leaks-part-1-writing-leaky-code.html
總結
以上是生活随笔為你收集整理的调查内存泄漏第1部分–编写泄漏代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (mtd linux)
- 下一篇: 海关如何备案商品(海关如何备案)