调查内存泄漏第1部分–编写泄漏代码
前幾天,我發(fā)現(xiàn)了這個(gè)小問題:該服務(wù)器運(yùn)行了一段時(shí)間,然后掉下來了。 然后通過啟動(dòng)腳本重新啟動(dòng),整個(gè)過程重復(fù)進(jìn)行。 這聽起來并不那么糟糕,盡管對數(shù)據(jù)的損失很大,但對業(yè)務(wù)的重要性并不重要,因此我決定仔細(xì)研究一下,找出問題出在哪里。 首先要注意的是,服務(wù)器通過了所有的單元測試和大量的集成測試。 它在使用測試數(shù)據(jù)的所有測試環(huán)境中都能很好地運(yùn)行,那么生產(chǎn)中出了什么問題? 很容易猜到,在生產(chǎn)中,它的負(fù)載可能比測試重,或者比設(shè)計(jì)所允許的負(fù)載大,因此它用盡了資源,但是什么資源?在哪里? 這是一個(gè)棘手的問題。
為了演示如何研究此問題,首先要做的是編寫一些泄漏的示例代碼,而我將使用Producer Consumer模式來執(zhí)行此操作,因?yàn)槲铱梢匝菔舅拇髥栴}。
為了演示泄漏的代碼1,我需要像往常一樣需要一個(gè)高度人為的方案,在這種情況下,您可以想象您在一個(gè)將股票銷售量記錄在數(shù)據(jù)庫中的系統(tǒng)上的股票經(jīng)紀(jì)人工作。 訂單由一個(gè)簡單的線程接收并放入隊(duì)列中。 然后,另一個(gè)線程從隊(duì)列中獲取訂單,并將其寫入數(shù)據(jù)庫。 的
Order POJO非常簡單,如下所示:
Order POJO是一個(gè)簡單的Spring應(yīng)用程序的一部分,該應(yīng)用程序具有三個(gè)關(guān)鍵抽象,當(dāng)Spring調(diào)用它們的start()方法時(shí),它們會(huì)創(chuàng)建一個(gè)新線程。
其中第一個(gè)是OrderFeed 。 它的run()方法創(chuàng)建一個(gè)新的虛擬訂單并將其放置在隊(duì)列中。 然后,它會(huì)休眠一會(huì)兒,然后再創(chuàng)建下一個(gè)訂單。
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 ,它負(fù)責(zé)從隊(duì)列中獲取訂單并將其寫入數(shù)據(jù)庫。 問題在于將訂單寫入數(shù)據(jù)庫要花費(fèi)的時(shí)間要長得多。 我的recordOrder(…)方法中有1秒的長時(shí)間睡眠,這證明了這一點(diǎn)。
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); } }結(jié)果很明顯: OrderRecord線程無法跟上,隊(duì)列將越來越長,直到JVM用完堆空間并OrderRecord為止。 這是生產(chǎn)者-消費(fèi)者模式的最大問題:消費(fèi)者必須能夠跟上生產(chǎn)者的步伐。
為了證明他的觀點(diǎn),我添加了第三類OrderMonitor ,該類每隔幾秒鐘打印一次隊(duì)列大小,以便您可以看到出現(xiàn)問題的地方。
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>下一步是啟動(dòng)泄漏的示例代碼。 您可以通過轉(zhuǎn)到以下目錄來執(zhí)行此操作
/<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文件的路徑
有一兩件事,我真的很討厭關(guān)于Java的是,事實(shí)上,它是如此難以運(yùn)行在命令行中的任何程序。 您必須弄清楚什么是類路徑,需要設(shè)置哪些選項(xiàng)和屬性以及什么是主類。 當(dāng)然,肯定有可能想到一種簡單地鍵入Java programName的方法,并且JVM找出所有內(nèi)容在哪里,特別是如果我們開始使用約定而不是配置:它有多難?
您還可以通過附加一個(gè)簡單的jconsole來監(jiān)視泄漏的應(yīng)用程序。 如果要遠(yuǎn)程運(yùn)行它,則需要在上面的命令行中添加以下選項(xiàng)(選擇您自己的端口號(hào)):
-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…如果您查看使用的堆數(shù)量,您會(huì)發(fā)現(xiàn)隨著隊(duì)列變大,堆逐漸增加。
如果一千字節(jié)的內(nèi)存泄漏了,那么您可能永遠(yuǎn)也找不到它。 如果一千兆字節(jié)的內(nèi)存泄漏,問題將很明顯。 因此,目前要做的只是坐下來等待一些內(nèi)存泄漏,然后再繼續(xù)進(jìn)行下一步調(diào)查。 下次再說…
1源代碼可以在我在GitHub上的Producer Consumer項(xiàng)目中找到 。
翻譯自: https://www.javacodegeeks.com/2013/12/investigating-memory-leaks-part-1-writing-leaky-code.html
總結(jié)
以上是生活随笔為你收集整理的调查内存泄漏第1部分–编写泄漏代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (mtd linux)
- 下一篇: Orika:将JAXB对象映射到业务/域