在建模時,通常需要優化模型的性能。要么通過提高內存利用率要么提高CPU的利用率。
然而,僅通過代碼審查或手動試錯更改,幾乎是不可能完成的。幸運的是,Java應用程序有一個特定的工具可以幫助您,讓工作變得更輕松。
因此,對于如何使用分析器分析模型性能這一主題,我發布了由 2 篇文章組成的系列文,本文是其中的第1篇,主要介紹了如何改善內存消耗和如何檢測內存泄漏。
下面讓我們看一個檢測內存泄漏和內存消耗的示例。
一、示例
讓我們看看下面這個超級簡單的仿真模型。我們有一個超基礎的仿真模型系統,其中包含智能體進入隊列,接受服務,然后退出系統。如果智能體在系統中等待太長時間,它會過早離開隊列,無法獲得服務,然后退出系統。
我們想測量得到服務的智能體在系統中花費的時間,因此我們在服務塊前后為過早退出系統的對象添加了時間測量開始和時間測量結束塊。
這種基本的服務流程仿真可以代表許多現實生活中的流程,例如,呼叫中心,呼叫者只愿意為服務等待一定的時間,或者洗車中心,潛在的顧客只會在隊列中等待幾分鐘,然后決定跳過洗車日。
附言:如果您認為自己是AnyLogic高級用戶,您應該能夠發現上述模型中的問題以及它將對您的模型性能產生的影響。 花點時間做一個預測,并在下面的評論部分寫下你的預測結果。
該模型非常簡單,我們計劃對其進行大的改進,添加大量細節和功能。但在此之前,讓我們快速運行一整年,并檢查結果。
這就是。。。
一切看起來都很好,模型在不到9秒的時間內完成了全年,結果看起來不錯。。。但是等等,看看消耗500MB內存。我們甚至還沒有開始為模型添加重要的細節!在我們將智能體的數量增加了一倍,并添加了大量的統計數據和額外的邏輯之后,我們究竟將如何運行該模型。。。。
二、查找內存泄漏
幸運的是,優化java應用程序性能已經存在很長時間了。我們不需要依靠自己的直覺或反復試驗來嘗試和優化我們的仿真模型。
有許多商業工具可以用于Java應用程序分析,我更喜歡eJ Technologies的一個叫做Jprofiler的工具。
然而,如果您是初學者,您可能不想在一個您不知道如何使用的工具上花錢。幸運的是,像Visual VM這樣的免費應用程序可以在這里下載到Mac和Windows上。
提示僅供參考:使用VisualVM Monitor視圖監視應用程序
CPU使用率:此圖表繪制了一段時間內的CPU使用率,用于指示模型需求何時使CPU過載
堆:堆圖顯示總堆大小和當前使用的堆數量。堆類似于內存
類:類圖顯示加載類和共享類總數的概述。
線程:線程圖顯示了應用程序JVM中活動線程和守護進程線程數量的概述。如果您想在特定時間點捕獲和查看應用程序線程上的精確數據,可以使用VisualVM進行線程轉儲。
什么是可視虛擬機
使用Visual VM檢測內存泄漏
使用以下步驟對應用程序進行內存分析,或觀看此處的視頻以獲取逐步說明
1) 運行模型,直到內存消耗出現問題,然后暫停模型。在我們的例子中,這是在模型端
2) 打開Visual VM應用程序。
3) 在左側窗格中,您將看到兩個應用程序鏈接到您的模型。AnyLogic數據庫以及模型本身。
4) 雙擊與模型關聯的應用程序,然后選擇右側窗格中的“monitor”選項卡
從概述統計數據中可以看出,我們使用的是零CPU,這在模型完成運行時是有意義的。然而,我們幾乎消耗了所有可用的500 MB內存。
提示:使用VisualVM視圖監視應用程序
CPU使用率:此圖表繪制了一段時間內的CPU使用率,用于指示模型需求何時使CPU過載
堆:堆圖顯示總堆大小和當前使用的堆數量。堆類似于內存
類:類圖顯示加載類和共享類總數的概述。
線程:線程圖顯示了應用程序JVM中活動線程和守護進程線程數量的概述。如果您想在特定時間點捕獲和查看應用程序線程上的精確數據,可以使用VisualVM進行線程轉儲。
5) 導航到Sampler頁面,在sample部分選擇memory,暫停分析器并選擇堆轉儲
提示:內存分析
在內存分析模式下,VisualVM顯示由應用程序加載的每個類分配的對象總數。對于Java虛擬機(JVM)中加載的每的大小和數量。在分配新對象和加載新類時,結果會自動更新。分配的字節也顯示為一個圖形,表示字節的百分比以及每個類分配的字節總數
6) 在堆轉儲頁面上,導航到對象視圖以分析堆轉儲
現在,這是一個有點棘手的地方,您需要獲得一些快速查找泄漏的經驗。最好的建議是從dump的頂部開始,按照自己的方式工作,直到找到有意義的東西,然后可以更改模型。鑒于AnyLogic包含許多您無法控制的內部功能和特性,您可能經常會遇到無法進行任何更改的可能問題。繼續,直到找到所創建模型的部分。
為了更快地解決問題,您需要計算對象的保留大小,并對其進行相應排序。默認情況下不會計算保留大小,但按此列排序將為您計算保留大小。
這可能需要一段時間。。。因此,在屏幕底部的狀態欄中跟蹤進度。。。
完成后,您將看到以下內容。
有98k個LinkedHashMap對象,占500MB大小的大部分
有一個TimeMeasureStart對象,它也有大約500MB的內存
還有98k LinkedHashMap$Entryfields,也有大約500MB的內存
系統中有97k輛汽車,也占了大約500MB的內存
有幾個跡象表明,這些事情是相關的,內存大小的保留。它必須是相關的,不能相互排斥;否則總內存消耗將遠遠超過2 GB。實例數量也令人懷疑。
奇怪的是,為什么有這么多關于Car對象的引用。即使所有的車都在水槽里被毀了。。。當模型結束時,系統中只有四輛車。。。。(第一個紅燈)
讓我們從占據最大尺寸的對象開始,在本例中是LinkedHashMap。如果我們深入研究,我們會嘗試查看哪些對象保留了對此對象的引用。
正如所懷疑的那樣,TimeMeasureStart保留了對這個LinkedHashMap的引用。
讓我們看看TimeMeasureStart。由于這個對象只有一個實例,我們將查看它的字段,看看是什么導致這個對象消耗了這么多內存。。。再往下看。。它似乎與我們在上一個屏幕截圖中看到的消耗了最多內存的hashMap(#1551)相同。
現在我們知道TimeMeasureStart對象保留了太多對某個對象的引用。。。因為我們知道這是衡量汽車進入時間的,我們在系統中有97976輛汽車,而預期只有4輛,我們可以假設它保留了對汽車的引用,即使它們在水槽中被摧毀了。。。
當你意識到所有通過隊列超時退出的汽車仍然被TimeMeasureStart作為參考。。。因為他們從來沒有通過過TimeMeasurerEnd。
模型內存中的汽車總數等于系統中當前的汽車數量+通過隊列超時退出的汽車數量。
三、修復泄漏
現在,我們確切地知道問題在哪里,修復泄漏非常容易。我們只需在隊列超時出口連接器中添加一個timemeasurend。
現在運行該模型,我們看到內存消耗已從500 MB下降到約250MB
哪一個更好,但不是很好。。。那現在怎么辦?
關于Java,需要記住的最后一件事是垃圾收集器(GC)僅在需要時運行。垃圾回收是指清除內存中沒有活動引用的所有對象。因此,所有正在使用的東西都被保留,那些不需要的東西被廢置。由于我們只有50%的內存使用率,GC可能就是這種情況。
在Visual VM內進行分析之前,可以強制垃圾收集。
這樣做內存消耗只有4%,這很可能是運行模型所需的最低內存消耗。在我們模型的早期版本上強制垃圾收集會導致內存減少,因為所有汽車對象在TimeMeasureStart對象內都有活動引用。
您可以在此處下載示例模型。