<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                第28章 享元模式 28.1 內存溢出,司空見慣 下午,我正在開會中,老大推門進來。 “三兒,出來一下。” 我剛出會議室門口,老大就發話了。 “郎當(姓朗,順口就叫郎當)的那個報考系統又crash了一臺機器,兩天已經宕了4次了,你這邊還有緊急的事情沒有?……沒有,那趕快過去頂一下,就運行三天的程序,兩天宕了4次,還怎么玩?!” 我馬上收拾東西,沖到馬路上攔了出租車,同時打電話給郎當。 “三哥,廠商人員已經定位出了,OutOfMemory內存溢出,沒查到有內存泄漏的情況,現在還在跟蹤……是突然暴漲的,都是在繁忙期出現問題的……” 內存溢出對Java應用來說實在是太平常了,有以下兩種可能。 ● 內存泄漏 無意識的代碼缺陷,導致內存泄漏,JVM不能獲得連續的內存空間。 ● 對象太多 代碼寫得很爛,產生的對象太多,內存被耗盡。現在的情況是沒有內存泄漏,那只有一種原因——代碼太差把內存耗盡。 到現場后,郎當給我介紹了一下系統情況。該系統是一個報考系統,其中有一個模塊負責社會人員報名,該模塊對全國的考試人員只開放3天,并且限制報考人員數量。第一天9點開始報考,系統慢得像蝸牛,基本上都不能訪問,后來設置了HTTP Server的并發數量,稍有緩解,40分鐘后宕了一臺機器,10分鐘后,又掛了一臺,下午3點又掛了一臺,看樣子晚上要讓郎當去寺廟燒燒香了。 該系統一共有8臺應用服務器,基本上CPU繁忙程度都在60%以上,HTTP的最大并發是2000,平均分配到每臺應用服務器上沒有太大的壓力,于是懷疑是代碼問題,然后詳細了解了一下業務和數據流邏輯,基本的業務操作過程清楚了,先登錄(沒有賬號的,則要先注冊),登錄后,需要填寫以下信息: ● 考試科目,選擇框。 ● 考試地點,選擇框,根據科目不同,列表不同。 ● 準考證郵寄地址,輸入框。 還有其他一堆信息,我們以這三者作為代表來講解。信息填寫完畢后,點擊確認,報名就結束了。簡單程序的業務邏輯也確實是這樣,為什么出現Crash情況呢?那肯定是和壓力有關系! 我們先把這個過程的靜態類圖畫出來,如圖28-1所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036bd75d3.jpg) 圖28-1 報考系統類圖 很簡單的工廠方法模式,表現層通過工廠方法模式創建對象,然后傳遞給業務層和持久層,最終保存到數據庫中,為什么要使用工廠方法模式而不用直接new一個對象呢?因為是在框架下編程,必須有一個對象工廠(ObjectFactory,Spring也有對象工廠)。我們先來看報考信息,如代碼清單28-1所示。 代碼清單28-1 報考信息 public?class?SignInfo?{ ?????//報名人員的ID ?????private?String?id; ?????//考試地點 ?????private?String?location; ?????//考試科目 ?????private?String?subject; ?????//郵寄地址 ?????private?String?postAddress; ?????public?String?getId()?{ ?????????????return?id; ?????} ?????public?void?setId(String?id)?{ ?????????????this.id?=?id; ?????} ?????public?String?getLocation()?{ ?????????????return?location; ?????} ?????public?void?setLocation(String?location)?{ ?????????????this.location?=?location; ?????} ?????public?String?getSubject()?{ ?????????????return?subject; ?????} ?????public?void?setSubject(String?subject)?{ ?????????????this.subject?=?subject; ?????} ?????public?String?getPostAddress()?{ ?????????????return?postAddress; ?????} ?????public?void?setPostAddress(String?postAddress)?{ ?????????????this.postAddress?=?postAddress; ?????} } 它是一個很簡單的POJO對象(Plain Ordinary Java Object,簡單Java對象)。我們再來看工廠類,如代碼清單28-2所示。 代碼清單28-2 報考信息工廠 public?class?SignInfoFactory?{ ?????//報名信息的對象工廠 ?????public?static?SignInfo?getSignInfo(){ ?????????????return?new?SignInfo(); ?????} } 工廠類就這么簡單?非也,這是我們的教學代碼,真實的ObjectFactory要復雜得多,主要是注入了部分Handler的管理。表現層是如何創建對象的,如代碼清單28-3所示。 代碼清單28-3 場景類 public?class?Client?{ ?????public?static?void?main(String[]?args)?{ ?????????????//從工廠中獲得一個對象 ?????????????SignInfo?signInfo?=?SignInfoFactory.getSignInfo(); ?????????????//進行其他業務處理 ?????} } 就這么簡單,但是簡單為什么會出現問題呢?而且這樣寫也沒有問題呀,很標準的工廠方法模式,應該不會有大問題,然后又看了看系統廠商提供的分析報告,報告中指出:內存突然由800MB飆升到1.4GB,新的對象申請不到內存空間,于是出現OutOfMemory,同時報告中還列出宕機時刻內存中的對象,其中SignInfo類的對象就有400MB,瘋子,絕對是瘋子!報告都沒有看嘛! 問題找到了,我拉郎當過來談話,“廠商不是分析出原因了嘛,人家已經指出SignInfo類的對象占用了400MB多的內存,這是怎么回事?” “三哥,這是很正常的,這么大的訪問量,產生出這么多的SignInfo對象也是應該的,內存中有這么多對象并不表示這些對象正在被使用呀,估計很大一部分還沒有被回收而已,垃圾回收器什么時候回收內存中的對象這是不確定的。你看,并發200多個,這可是并發數量……” 我想了想,也確實是這么回事。既然已經定位是內存中對象太多,那就應該想到使用一種共享的技術減少對象數量,那怎么共享呢? 大家知道,對象池(Object Pool)的實現有很多開源工具,比如Apache的commons-pool就是一個非常不錯的池工具,我們暫時還用不到這種重量級的工具,我們自己來設計一個共享對象池,需要實現如下兩個功能。 ● 容器定義 我們要定義一個池容器,在這個容器中容納哪些對象。 ● 提供客戶端訪問的接口 我們要提供一個接口供客戶端訪問,池中有可用對象時,可以直接從池中獲得,否則建立一個新的對象,并放置到池中。 設計思路有了,那我們池中對象的標準是什么呢?你想想看,如果你把所有的對象都放到池中,那還有什么意義?內存早就給你撐爆了!這么多對象,必然有一些相同的屬性值,如幾十萬SignInfo對象中,考試科目就4個,考試地點也就是30多個,其他的屬性則是每個對象都不相同的,我們把對象的相同屬性提取出來,不同的屬性在系統內進行賦值處理,是不是就可以建立一個池了?話無須多說,我們以類圖來表示,如圖28-2所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036c0072a.jpg) 圖28-2 增加對象池的類圖 做一個很小的改動,增加了一個子類,實現帶緩沖池的對象建立,同時在工廠類上增加了一個容器對象HashMap,保存池中的所有對象。我們先來看產品子類,如代碼清單28-4所示。 代碼清單28-4 帶對象池的報考信息 public?class?SignInfo4Pool?extends?SignInfo?{ ?????//定義一個對象池提取的KEY值 ?????private?String?key; ?????//構造函數獲得相同標志 ?????public?SignInfo4Pool(String?_key){ ?????????????this.key?=?_key; ?????} ?????public?String?getKey()?{ ?????????????return?key; ?????} ?????public?void?setKey(String?key)?{ ?????????????this.key?=?key; ?????} } 很簡單,就是增加了一個key值,為什么要增加key值?為什么要使用子類,而不在SignInfo類上做修改?好,我來給你解釋為什么要這樣做,我們剛剛已經分析了所有的SignInfo對象都有一些共同的屬性:考試科目和考試地點,我們把這些共性提取出來作為所有對象的外部狀態,在這個對象池中一個具體的外部狀態只有一個對象。按照這個設計,我們定義key值的標準為:考試科目+考試地點的復合字符串作為唯一的池對象標準,也就是說在對象池中,一個key值唯一對應一個對象。 注意 在對象池中,對象一旦產生,必然有一個唯一的、可訪問的狀態標志該對象,而且池中的對象聲明周期是由池容器決定,而不是由使用者決定的。 你可能馬上就要提出了,為什么不建立一個新的類,包含subject和location兩個屬性作為外部狀態呢?嗯,這是一個辦法,但不是最好的辦法,有兩個原因: ● 修改的工作量太大,增加的這個類由誰來創建呢?同時,SignInfo類是否也要修改呢?你不可能讓兩段相同的POJO程序同時出現在同一模塊中吧! ● 性能問題,我們會在擴展模塊中講解。 說了這么多,我們還是繼續來看程序,工廠類如代碼清單28-5所示。 代碼清單28-5 帶對象池的工廠類 public?class?SignInfoFactory?{ ?????//池容器 ?????private?static?HashMap<String,SignInfo>?pool?=?new?HashMap<String,SignInfo>(); ?????//報名信息的對象工廠 ?????@Deprecated ?????public?static?SignInfo(){ ??????????return?new?SignInfo(); ?????} ?????//從池中獲得對象 ?????public?static?SignInfo?getSignInfo(String?key){ ??????????//設置返回對象 ??????????SignInfo?result?=?null; ??????????//池中沒有該對象,則建立,并放入池中 ??????????if(!pool.containsKey(key)){ ???????????????System.out.println(key?+?"----建立對象,并放置到池中"); ???????????????result?=?new?SignInfo4Pool(key); ???????????????pool.put(key,?result); ??????????}else{ ???????????????result?=?pool.get(key); ???????????????System.out.println(key?+"---直接從池中取得"); ??????????} ??????????return?result; ?????} } 方法都很簡單,不多解釋。讀者需要注意一點的是@Deprecated注解,不要有刪除投產中代碼的念頭,如果方法或類確實不再使用了,增加該注解,表示該方法或類已經過時,盡量不要再使用了,我們應該保持歷史原貌,同時也有助于版本向下兼容,特別是在產品級研發中。 我們再來看看客戶端是如何調用的,如代碼清單28-6所示。 代碼清單28-6 場景類 public?class?Client?{ ?????public?static?void?main(String[]?args)?{ ??????????//初始化對象池 ??????????for(int?i=0;i<4;i++){ ???????????????String?subject?=?"科目"?+?i; ???????????????//初始化地址 ???????????????for(int?j=0;j<30;j++){ ????????????????????String?key?=?subject?+?"考試地點"+j; ????????????????????SignInfoFactory.getSignInfo(key); ???????????????} ??????????} ??????????SignInfo?signInfo?=?SignInfoFactory.getSignInfo("科目1考試地點1"); ?????} } 運行結果如下所示: 科目3考試地點25----建立對象,并放置到池中 科目3考試地點26----建立對象,并放置到池中 科目3考試地點27----建立對象,并放置到池中 科目3考試地點28----建立對象,并放置到池中 科目3考試地點29----建立對象,并放置到池中 科目1考試地點1---直接從池中取得 前面還有很多的對象創建提示語句,不再復制。通過這樣的改造后,我們想想內存中有多少個SignInfo對象?是的,最多120個對象,相比之前幾萬個SignInfo對象優化了非常多。細心的讀者可能注意到了SignInfo4Pool類基本上沒有跑出我們的視線范圍,僅僅在工廠方法中使用到了,盡量縮小變更引起的風險,想想看我們的改動是不是很小,只要在展示層中拼一個字符串,然后傳遞到工廠方法中就可以了。 通過這樣的改造后,第三天系統運行得非常穩定,CPU占用率也下降了,而且以后再也沒有出現類似問題,這就是享元模式的功勞。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看