1. 程式人生 > >wabacus框架在Myeclipse reload過程中方法區溢位問題討論

wabacus框架在Myeclipse reload過程中方法區溢位問題討論

  1. 問題解析
  2. 總結

    1. 問題解析
      (下面的文章都是基於個人的知識,由於本人是個菜鳥,歡迎指正)
      現在手上有一個比較簡單的資訊管理系統的小專案,最開始立項的時候我還沒有來到學校,已經定下來採用wabacus框架去做,至於wabacus框架是個什麼東西,詳情請點選
    Wabacus框架,是一個能大大提高J2EE專案開發效率的通用快速開發框架,與ExtJs,JQuery等純客戶端框架不同, 它提供的是前後臺的完整解決方案,可以完成SSH框架的功能,但是開發效率比它快好幾倍,因為基本上不用編寫JSP/JAVA程式碼,或只要編寫很少的程式碼。 ----摘自wabacus官網

裡面提到的不用或者很少,是建立在你有強大的sql編寫能力的基礎上。整個框架是採用xml檔案配置的方式,配置一些例如報表的列及其屬性,資料來源,js,css等,通過框架解析成web的前後臺形式。

最近在開發的時候,jvm的方法區大小沒有手動去調整,預設80m。經過幾次改動並reload時,在方法區發生了oom。目前為止,該錯誤只出現了開發中redeploy時,在正常的專案測試中tomcat關閉時都會出現這個提示。經過檢視tomcat給出的日誌,發現在redeploy時有提示兩個執行緒未正常退出,可能會造成記憶體洩漏。

六月 04, 2016 9:56:59 上午 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
嚴重: The web application [/MySystem] appears to
have started a thread named [Thread-1] but has failed to stop it. This is very likely to create a memory leak. 六月 04, 2016 9:56:59 上午 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 嚴重: The web application [/MySystem] appears to have started a thread named [Thread-2] but has failed to
stop it. This is very likely to create a memory leak.

首先要找到這兩個執行緒因為什麼沒有退出。
通過jps命令找到這兩個執行緒坐在的程序id,在通過jstack命令檢視該程序中的所有執行緒狀態。如下圖
7748 是兩個執行緒所在的程序id
下圖是Thread-1的詳情,在使用jstack命令檢視執行緒時發現,thread-2執行緒開始處於sleep狀態,一段時間後退出,後續輝仔框架原始碼解析中給出解釋。先看Thread-1.Thread-1,根據jsatck命令檢視到的資訊,可以初步的判斷,該程序出入waiting狀態無法退出,更具提示資訊發現是wabacus框架提供的一個關於檔案上傳的程序一直出入等待狀態無法退出,根據提示,去檢視wabacus原始碼中具體是怎麼實現的。

關於wabacus的啟動,這裡做一下簡單的介紹,在我淺顯的理解基礎上!在web.xml中能看到下面程式碼,順藤摸瓜,找到該listener原始碼。

 <listener>
  <listener-class>com.wabacus.WabacusServlet</listener-class>
 </listener>

com.wabacus.WabacusServlet 原始碼

public class WabacusServlet extends HttpServlet implements ServletContextListener//既是一個Listener又是一個Servlet的,Servlet的主要作用,估計是為了框架實現熱部署,這點有待後續驗證·····
 public void contextInitialized(ServletContextEvent event)
    {
        closeAllDatasources();
        Config.homeAbsPath=event.getServletContext().getRealPath("/");
        Config.homeAbsPath=FilePathAssistant.getInstance().standardFilePath(Config.homeAbsPath+"\\");
        /*try
        {
            Config.webroot=event.getServletContext().getContextPath();
            if(!Config.webroot.endsWith("/")) Config.webroot+="/";
        }catch(NoSuchMethodError e)
        {
            Config.webroot=null;
        }*/
        Config.webroot=null;
        Config.configpath=event.getServletContext().getInitParameter("configpath");
        if(Config.configpath==null||Config.configpath.trim().equals(""))
        {
            log.info("沒有配置存放配置檔案的根路徑,將使用路徑:"+Config.homeAbsPath+"做為配置檔案的根路徑");
            Config.configpath=Config.homeAbsPath;
        }else
        {
            Config.configpath=WabacusAssistant.getInstance().parseConfigPathToRealPath(
                    Config.configpath,Config.homeAbsPath);
        }
        loadReportConfigFiles();
        FileUpDataImportThread.getInstance().start();
        TimingThread.getInstance().start();


    }

分析上面主要程式碼,上來首先在載入xml的時候就關閉了資料來源closeAllDatasources(),應該是在實現框架熱部署時,如果修改了xml裡面資料來源的配置,首先要關閉資料來源,重新載入。之後又做了一些路徑上的處理。
最後三個方法比較重要。

        //載入所有報表配置檔案
        loadReportConfigFiles();
        //開啟一個執行緒,初步判斷是用來處理檔案上傳的,後續後深入剖析。
        FileUpDataImportThread.getInstance().start();
        //開啟定時執行緒,初步判斷是用來一定時間內完成對資料的備份
        TimingThread.getInstance().start();

本文章所要解決的問題就在最後兩個執行緒上,關於報表的載入輝仔後續文章中剖析。在看一下銷燬方法。

 public void contextDestroyed(ServletContextEvent event)
    {
        //關閉資料來源
        closeAllDatasources();
        //停止執行緒
        FileUpDataImportThread.getInstance().stopRunning();
        //停止執行緒
        TimingThread.getInstance().stopRunning();

    }

    private void closeAllDatasources()
    {
        Map<String,AbsDataSource> mDataSourcesTmp=Config.getInstance().getMDataSources();
        if(mDataSourcesTmp!=null)
        {
            for(Entry<String,AbsDataSource> entry:mDataSourcesTmp.entrySet())
            {
                if(entry.getValue()!=null)
                    entry.getValue().closePool();
            }
        }
    }

問題就出在了listener銷燬時,兩個執行緒沒有正常退出。下面對第一個執行緒原始碼做剖析。

public class FileUpDataImportThread extends AbsDataImportThread

AbsDataImportThread沒多少東西就是一個繼承了Thread的很簡單的抽象類

public abstract class AbsDataImportThread extends Thread
{
    protected boolean RUNNING_FLAG=true;

    public void restart()
    {
        RUNNING_FLAG=true;
        start();
    }

    public void stopRunning()
    {
        RUNNING_FLAG=false;
    }
}

FileUpDataImportThread 原始碼

    private final static FileUpDataImportThread instance=new FileUpDataImportThread();

    private FileUpDataImportThread()
    {}

    public static FileUpDataImportThread getInstance()
    {
        return instance;
    }

單例設計模式,採用的是餓漢式單例,天生安全。

    public void run()
    {
        while(RUNNING_FLAG)
        {
            try
            {
                List<Map<List<DataImportItem>,Map<File,FileItem>>> lstUploadFiles=UploadFilesQueue
                        .getInstance().getLstAllUploadFiles();
                log.debug("上傳檔案執行緒啟動,正在進行檔案上傳.........................");
                for(Map<List<DataImportItem>,Map<File,FileItem>> mUploadFilesTmp:lstUploadFiles)
                {
                    if(mUploadFilesTmp.size()==0) continue;
                    Entry<List<DataImportItem>,Map<File,FileItem>> entry=mUploadFilesTmp.entrySet().iterator().next();
                    doDataImport(entry.getKey(),entry.getValue());
                }
            }catch(Exception e)
            {
                log.error("資料匯入執行緒執行失敗",e);
            }
        }
    }
    public void stopRunning()
    {
        super.stopRunning();
        UploadFilesQueue.getInstance().notifyAllThread();
    }

上面是run和stopRunning方法,通過RUNNING_FLAG標誌安全退出執行緒,看似沒問題,那就是UploadFilesQueue.getInstance().notifyAllThread();出問題了。
框架提供了一個可以通過上傳excel檔案完成批量資料的匯入,lstUploadFiles=UploadFilesQueue .getInstance().getLstAllUploadFiles();是獲取上傳檔案佇列中的檔案,然後呼叫doDataImport方法完成資料的匯入,這裡不做過多解釋,主要看一下執行緒問題。UploadFilesQueue採用的也是單例模式,在getLstAllUploadFiles()函式裡面,出現問題了,看原始碼。

 public List<Map<List<DataImportItem>,Map<File,FileItem>>> getLstAllUploadFiles()
    {
        synchronized(queueInstance)
        {
            while(queueInstance.size()==0)
            {
                try
                {
                    queueInstance.wait();
                }catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            List<Map<List<DataImportItem>,Map<File,FileItem>>> lstResults=new ArrayList<Map<List<DataImportItem>,Map<File,FileItem>>>();
            lstResults.addAll(queueInstance);
            queueInstance.clear();
            return lstResults;
        }
    }

    public void notifyAllThread()
    {
        synchronized(queueInstance)
        {
            queueInstance.notifyAll();
        }
    }

當queueInstance佇列為空時,執行緒wait等待使用者上傳檔案,一旦使用者上傳檔案,跳出while,將檔案新增到了一個新的佇列中返回給FileUpDataImportThread,完成資料的匯入,這裡貌似沒有問題。但是在,FileUpDataImportThread執行緒退出時,UploadFilesQueue.getInstance().notifyAllThread();通知正在等待UploadFilesQueue狀態的執行緒退出waiting狀態,但是即使退出waiting狀態又如何呢,還是沒有跳出while迴圈,接下來在UploadFilesQueue是空的情況下,FileUpDataImportThread任然還是出入waiting中,稍加改動,如下。

public List<Map<List<DataImportItem>,Map<File,FileItem>>> getLstAllUploadFiles()
    {
        synchronized(queueInstance)
        {
            **while(Flag&&queueInstance.size()==0)**
            {
                System.out.println("我還沒有退出!");
                try
                {
                    queueInstance.wait();
                }catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("我wait結束了!");
            }
            System.out.println("我跳出了while迴圈!size:"+queueInstance.size());
            List<Map<List<DataImportItem>,Map<File,FileItem>>> lstResults=new ArrayList<Map<List<DataImportItem>,Map<File,FileItem>>>();
            lstResults.addAll(queueInstance);
            queueInstance.clear();
            return lstResults;
        }
    }

    public void notifyAllThread()
    {
        synchronized(queueInstance)
        {
            **Flag = false;**
            queueInstance.notifyAll();
        }
    }

對while迴圈新增一個flag,退出的時候改變flag狀態,個人認為凡是線上程中出現了不斷檢查狀態的while迴圈一般都需要加上一個flag作為退出標誌。

接下來分析Thread-2執行緒,也就是 TimingThread,在使用jstack命令檢視的時候,發現改執行緒處於sleep狀態。

    public void run()
    {
        while(RUNNING_FLAG)
        {
            if(this.lstTasks==null||this.lstTasks.size()==0) break;
            for(ITask taskObjTmp:lstTasks)
            {
                try
                {
                    if(taskObjTmp.shouldExecute()) taskObjTmp.execute();
                }catch(Exception e)
                {
                    log.error("執行任務:"+taskObjTmp.getTaskId()+"時失敗",e);
                }
            }
            if(this.intervalMilSeconds==Long.MIN_VALUE)
            {
                intervalMilSeconds=Config.getInstance().getSystemConfigValue("timing-thread-interval",15)*1000L;
                if(intervalMilSeconds<=0) intervalMilSeconds=15*1000L;
            }
            if(this.intervalMilSeconds>0)
            {
                try
                {
                    Thread.sleep(this.intervalMilSeconds);
                }catch(InterruptedException e)
                {
                    log.warn("wabacus定時執行執行緒被中斷睡眠狀態",e);
                }
            }
        }
    }

通過檢視原始碼,得知這個執行緒在使用者指定時間內執行一個指定的task,在reload或者專案關閉時,該執行緒仍然處於sleep狀態無法退出,這倆線上程退出方法中interrupt該執行緒,使該執行緒退出sleep,正常退出。

  public void stopRunning()
    {
        RUNNING_FLAG=false;
        interrupt();
        if(this.lstTasks!=null)
        {
            for(ITask taskObjTmp:lstTasks)
            {
                try
                {
                    taskObjTmp.destory();
                }catch(Exception e)
                {
                    log.error("停止任務:"+taskObjTmp.getTaskId()+"時失敗",e);
                }
            }
        }
    }

通過上面的分析,把修改後的框架編譯之後和之前的框架做一下對比,在reload和tomcat關閉時。下面是對比詳情。工具jvisualvm。
原框架,專案執行起來後,PermGen使用情況
這裡寫圖片描述
reload 2次後PermGen使用情況
這裡寫圖片描述
類裝載和解除安裝情況
這裡寫圖片描述
reload 4次後PermGen使用情況
這裡寫圖片描述
類裝載和解除安裝情況
這裡寫圖片描述

此時在框架載入框架所需要的類檔案時出現PermGen的oom。

java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:643)
    at com.wabacus.util.WabacusClassLoader.loadClass(WabacusClassLoader.java:110)
    at com.wabacus.system.assistant.ReportAssistant.buildPOJOClass(ReportAssistant.java:461)
    at com.wabacus.system.assistant.ReportAssistant.buildReportPOJOClass(ReportAssistant.java:407)
    at com.wabacus.config.component.application.report.ReportBean.loadPojoClass(ReportBean.java:1504)
    at com.wabacus.config.component.application.report.ReportBean.doPostLoad(ReportBean.java:1431)
    at com.wabacus.config.component.container.AbsContainerConfigBean.doPostLoad(AbsContainerConfigBean.java:435)
    at com.wabacus.config.component.container.panel.TabsPanelBean.doPostLoad(TabsPanelBean.java:191)
    at com.wabacus.config.component.container.AbsContainerConfigBean.doPostLoad(AbsContainerConfigBean.java:435)
    at com.wabacus.config.component.container.page.PageBean.doPostLoad(PageBean.java:330)
    at com.wabacus.config.ConfigLoadManager.loadAllReportSystemConfigs(ConfigLoadManager.java:203)
    at com.wabacus.WabacusServlet.loadReportConfigFiles(WabacusServlet.java:112)
    at com.wabacus.WabacusServlet.contextInitialized(WabacusServlet.java:79)

原框架,專案執行起來後,PermGen使用情況
這裡寫圖片描述
reload 2次後PermGen使用情況
這裡寫圖片描述
類裝載和解除安裝情況
這裡寫圖片描述
reload 4次後PermGen使用情況
這裡寫圖片描述
類裝載和解除安裝情況
這裡寫圖片描述

第四次reload後,出現了PermGen的垃圾回收,大量類被解除安裝,從堆的dump檔案上也可以看出,原框架大量類沒有被解除安裝。下面是對比圖
原框架。reload2次後
這裡寫圖片描述
修改後,reload4次後
這裡寫圖片描述

  1. 總結
    在reload和tomcat關閉時,記憶體洩漏的提示也沒有了,從之前的提示上來看,個人感覺是執行緒為正常退出,導致classloader不能夠被解除安裝,從而導致classloader載入的類不能被解除安裝,導致方法區記憶體溢位。
    對於本文討論的兩個執行緒,感覺處於sleep狀態的執行緒,在一段時間後會自動退出,影響沒有那麼大,而waiting狀態的程序,是不可能退出的,在reload時會導致記憶體的洩漏。而在tomcat關閉後能夠順利退出,因為兩個程序都是daemon程序,在程式退出後,執行緒也就退出了。但是,在這裡,兩個執行緒應不應該是daemon執行緒,待後續深入兩個執行緒到底幹了什麼後在討論。

相關推薦

wabacus框架Myeclipse reload過程方法溢位問題討論

問題解析 總結 問題解析 (下面的文章都是基於個人的知識,由於本人是個菜鳥,歡迎指正) 現在手上有一個比較簡單的資訊管理系統的小專案,最開始立項的時候我還沒有來到學校,已經定下來採用wabacus框架去做,至於wabacus框架是個什麼東西,詳情請點選。

對於JVM方法,永久代,元空間以及字符串常量池的遷移和string.intern方法

ase ane 虛擬機 影響 一個 tle 自定義類加載器 機器 img 在Java虛擬機(以下簡稱JVM)中,類包含其對應的元數據,比如類的層級信息,方法數據和方法信息(如字節碼,棧和變量大小),運行時常量池,已確定的符號引用和虛方法表。 在過去(當自定義類加載器使用

基於vue框架專案開發過程遇到的問題總結(一)

(一)關於computed修改data裡變數的值 問題:computed裡是不能直接修改data裡變數的值,否則在git commit 時會報錯 解決:在computed裡使用get和set來進行獲取和修改data變數,(參考下圖) (二)computed裡監聽陣列

分散式框架dubbo使用過程常見錯誤及解決

轉載自:http://www.cnblogs.com/digdeep/p/5268779.html 1. Caused by: java.lang.reflect.MalformedParameterizedTypeException 啟動時報錯,原因是dubbo 依

JVM調優——Java動態編譯過程的記憶體溢位問題

由於測試環境專案每2小時記憶體就溢位一次, 分析問題,發現Java動態載入Class並執行那塊存在記憶體溢位問題, 遂本地調測。 一、找到動態編譯那塊的程式碼,具體如下 /** * @MethodName : 編譯java程式碼到Object * @Descrip

zabbix   監控平臺搭建過程的報錯與解決方法總結

監控 zabbix 運維自動化1.php option post_max_size 2.php option max_execution_time 3.php option max_input_time 4.php time zone 5.php bcm

Nginx 安裝過程遇到的一些問題及解決方法

requires nbsp margin req 命令 裝包 ... check sbin 一、安裝 獲取安裝包: wget http://nginx.org/download/nginx-1.11.5.tar.gz 解壓安裝包: tar -zxvf nginx-1.11

SQLin參數在存儲過程傳遞及使用的方法

str pro let 字符 spl eva lec creat create 背景: 1、使用存儲過程 2、存儲過程中有in 3、in括號裏面的內容作為參數傳遞 解決方案: 1、直接拼接sql 可在存儲過程中拼接字符串,然後執行此字符串,類似於js中的eval PROC

Java:驗證在類繼承過程equals()、 hashcode()、toString()方法的使用

red ger 輸出 ria oid nag println manage base 以下通過實際例子對類創建過程匯中常用的equals()、hashcode()、toString()方法進行展示,三個方法的創建過程具有通用性,在項目中可直接改寫。 //通過超類Employ

EF(Linq)框架使用過程的小技巧匯總 dbfunctions

查詢 into keyword 日期 != 二次 without time() rom 這篇博客總結本人在實際項目中遇到的一些關於EF或者Linq的問題,作為以後復習的筆記或者供後來人參考(遇到問題便更新)。 目錄 技巧1: DbFunctions.TruncateTim

webpack 創建項目過程遇到的問題和解決方法

chunk 輸入 new pla webp ins gin try htm 1 webpack實現多個輸入輸出多個html entry:{ main: ‘./src/main.js‘, login: ‘./src/login.js‘

在 Linux redis 驗證交互連接過程遇到 redis Could not connect to Redis at 127.0.0.1:6379: Connection refused 的解決方法

bind 服務器 技術分享 nbsp ade 解決 報錯 發現 bar Could not connect to Redis at 127.0.0.1:6379: Connection refused 1.找到redis.conf 並修改 daemonize no

解決在onCreate()過程獲取View的width和Height為0的4種方法

得到 observer oba target 都沒有 重寫 idt tlist reat 此博客為轉載,原文請看這位老鐵的文章: https://www.cnblogs.com/kissazi2/p/4133927.html 很經常當我們動態創建某些View時,需要通過獲取

EF(Linq)框架使用過程的小技巧匯總

客戶 sele support dev 部分 inpu 解決 posit 整數 Ref. https://www.cnblogs.com/farb/p/EFSkillsCollection.html 這篇博客總結本人在實際項目中遇到的一些關於EF或者Linq的問題,作為

生產過程swap分滿了的解決辦法

swapon sin 解決 default mks http 磁盤 sina 配置文件 dd if =/dev/zero of=/swapfile bs=1M count=2048 #找出磁盤比較大的目錄,創建2g的空文件 mkswap /swapfile //

jvm內存模型-棧,方法,程序計數器是線程安全的

如同 其它 必須 lan tro 應用 之前 信息 大小 文章轉自 https://www.cnblogs.com/myna/p/7567889.html 引文 JDK7及之前版本的方法區(Method Area)和Java堆一樣,是各個線程共享的內存區域,用於存

java學習過程遇到的坑及解決方法

param bsp exception log 導入 學習 query data zha 1、 Table ‘my_data_base.gjp_zhangwu‘ doesn‘t exist Query: select * from gjp_zhangwu Parameter

mssql 存儲過程調用另一個存儲過程的結果的方法分享

nio 遇到 roc pre -- run 思路 mss union 摘要:下文將分享"一個存儲過程"中如何調用"另一個存儲過程的返回結果",並應用到自身的運算中 在實際開發中,我們經常會遇到在一個存儲過程中調用另一個存儲過程的返回結

keil mdk除錯過程檢視區域性變數的方法

      上次除錯STM32做了一次總結,此次在除錯nordic 51822時發現區域性變數地址給不出任何資訊, 導致無法檢視區域性變數值。通過和STM32的設定進行必較發現C/C++的編譯器等級設定過高,而將 一些區域性變數優化掉而沒有分配記憶體地址。

Oracle 11g 安裝過程“檢查網絡配置要求 未執行”解決方法

cal 這樣的 測試的 http 項目 align 網絡 net system 正在檢查網絡配置要求... 檢查完成。此次檢查的總體結果為: 未執行 網上查了一下,很多朋友都遇到這個問題而無從下手,其實解決起來很容易的。 只需要在 Windows XP 中安裝 Micro