• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            chenglong7997

            淺析JAVA之垃圾回收機(jī)制

              對于JAVA編程和很多類似CC++語言有一個(gè)巨大區(qū)別就是內(nèi)存不需要自己去free或者delete,而是由JVM垃圾回收機(jī)制去完成的。對于這個(gè)過程很多人一直比較茫然或者覺得很智能,使得在寫程序的過程不太考慮它的感受,其實(shí)知道一些內(nèi)在的原理,幫助我們編寫更加優(yōu)秀的代碼是非常有必要的。

            本文從以下幾個(gè)方面進(jìn)行闡述:
            1finalize()方法
            2System.gc()方法及一些實(shí)用方法
            3JAVA如何申請內(nèi)存,和CC++有何區(qū)別

            4JVM如何尋找到需要回收的內(nèi)存
            5JVM如何回收內(nèi)存的(回收算法分解詳述)
            6、應(yīng)用服務(wù)器部署及常用參數(shù)設(shè)置
            7、擴(kuò)展話題JIT(及時(shí)編譯技術(shù))與lazy evaluation(惰性評估)


            1
            finalize()方法:

                 為了說明JVM回收,不得不先說明一個(gè)問題就是關(guān)于finalize()方法,所有實(shí)體對象都會有這個(gè)方法,因?yàn)檫@個(gè)Object類定義的,這個(gè)可能會被認(rèn)為是垃圾回收的方法或者叫做析構(gòu)函數(shù),其實(shí)并非如此。finalizeJVM內(nèi)存回收前會被調(diào)用(但并非絕對),而即使不調(diào)用它,JVM回收機(jī)制通過后面所述的一些算法就可以定位哪些是垃圾內(nèi)存,那么這個(gè)拿來干什么用呢?
              
            finalize()其實(shí)是要做一些特殊的內(nèi)存回收操作,如果對JAVA研究稍微多一點(diǎn),大家會發(fā)現(xiàn)JAVA中有一種JNI的機(jī)制,即:Java native interface,這種屬于JAVA本地接口調(diào)用,即調(diào)用本地的其他語言信息,JAVA虛擬機(jī)底層掉調(diào)用也是這樣實(shí)現(xiàn)的,這部分調(diào)用中可能存在一些對CC++語言的操作,在CC++內(nèi)部通過newmallocrealloc等關(guān)鍵詞創(chuàng)建的對象垃圾回收機(jī)制是無能為力的,因?yàn)檫@不是它要管理的范圍,而平時(shí)這些對象可能被JAVA對應(yīng)的實(shí)體所調(diào)用,那么需要在對應(yīng)JAVA對象放棄時(shí)(并不代表回收,只是程序中不使用它了)去調(diào)用對應(yīng)的CC++提供的本地接口去釋放這段內(nèi)存信息,他們的釋放同樣需要通過freedelete去釋放,所以我們一般情況下不要濫用finalize()可能你會聯(lián)想到另一類某些特殊引用對象的釋放,如層數(shù)引用太多,JAVA虛擬機(jī)有些時(shí)候不知道這一線的對象是否都可能被回收那么,你可以自己將finalize()重寫,并將內(nèi)置對象的句柄先釋放掉,這樣也是沒有問題的,不過一般不要濫用而已。

            2System.gc()或者Runtime.getRuntime().gc();

               這個(gè)可以被認(rèn)為是強(qiáng)制垃圾回收的一種機(jī)制,但是并非強(qiáng)制回收,只是向JVM建議可以進(jìn)行垃圾回收,而且垃圾回收的地方和多少是不能像C語言一樣控制,這是JVM垃圾回收機(jī)去控制的。程序中盡量不要是去使用這些東西,除自己開發(fā)一些管理代碼除外,一般由JVM自己管理即可。

            這里順便提及幾個(gè)查看當(dāng)前JVM內(nèi)存的幾個(gè)方法(在同一個(gè)集群下的多個(gè)SERVER,即使在同一個(gè)機(jī)器上通過下面方法只能查看到當(dāng)前SERVER下的內(nèi)存情況):

            2.1.設(shè)置的最大內(nèi)存:-Xmx等值:

            (Runtime.getRuntime().maxMemory()/ (1024 * 1024)) + "MB"

            2.2.當(dāng)前JVM可使用的內(nèi)存,這個(gè)值初始化和-Xms等值,若加載東西超過這個(gè)值,那么以下值會跟著變大,不過上限為-Xmx,由于變動過程中需要由虛擬機(jī)向操作系統(tǒng)申請新的內(nèi)存,所以存在不連續(xù)內(nèi)存以及影響開銷,很多時(shí)候應(yīng)用軟件服務(wù)器提供商(如BEA)推薦是-Xms等價(jià)于-Xmx的值:

            (Runtime.getRuntime().totalMemory()/ (1024 * 1024)) + "MB"

            2.3.剩余內(nèi)存,在當(dāng)前可使用內(nèi)存基礎(chǔ)上,剩余內(nèi)存等價(jià)于其剪掉使用了的內(nèi)存容量:

            (Runtime.getRuntime().freeMemory()/ (1024 * 1024)) + "MB"

             同理如果要查看使用了多少內(nèi)存或者百分比。可以通過上述幾個(gè)參數(shù)進(jìn)行運(yùn)算查看到。。。。

            順便在這里提供幾個(gè)實(shí)用方法和類,這部分可能和JVM回收關(guān)系不大,不過只是相關(guān)推敲,擴(kuò)展知識面,而且也較為實(shí)用的東西:

            2.4.獲取JAVA中的所有系統(tǒng)級屬性值(包含虛擬機(jī)版本、操作系統(tǒng)、字符集等等信息):

             System.setProperty("AAA", "123445");

             Properties properties = System.getProperties();
              Enumeration<Object> e = properties.keys();
              while (e.hasMoreElements()) {
               String key = (String) e.nextElement();
               System.out.println(key + " = " + properties.getProperty(key));
             }
             

            2.5.獲取系統(tǒng)中所有的環(huán)境變量信息:

            Map<String, String> env = System.getenv();
              for (Iterator<String> iterator = env.keySet().iterator(); iterator.hasNext();) {
               String key = iterator.next();
               System.out.println(key + " = " + env.get(key));
            }

            System.out.println(System.getenv("CLASSPATH"));

            2.6.Win環(huán)境下,打開一個(gè)記事本和一個(gè)WORD文檔:

            try {

              Runtime.getRuntime().exec("notepad");

              Runtime.getRuntime().exec("cmd /c start Winword");

            }catch(Exception e) {

              e.printStackTrace();

            }

            2.7.查詢當(dāng)前SERVER下所有的線程信息列表情況(這里需要提供兩個(gè)步驟,首先要根據(jù)任意一個(gè)線程獲取到頂級線程組的句柄(有關(guān)線程的說明,后面專門會有一篇文章說明),然后通過頂級線程組得到其存在線程信息,進(jìn)行一份拷貝,給與遍歷):

            2.7.1.這里通過當(dāng)前線程得到頂級線程組信息:

            public static ThreadGroup getHeadThreadGroup() {
              Thread t = Thread.currentThread();
              ThreadGroup group = t.getThreadGroup();
              while(group.getParent() != null) {
               group = group.getParent();
              }
              return group;
            }

            2.7.2.通過得到的頂級線程組,遍歷存在的子元素信息(僅僅遍歷常用屬性):

            public static void disAllThread(ThreadGroup threadgroup) {
              Thread list[] = new Thread[threadgroup.activeCount()];
              threadgroup.enumerate(list);
              for(Thread thread:list) {
               System.out.println(thread.getId()+"\t"+thread.getName()

                                          +"\t"+thread.getThreadGroup()+"\t"

                                          +thread.getState()+"\t"+thread.isAlive());}
            }

            2.7.3.測試方法如:

            類名.disAllThread(getHeadThreadGroup());即可完成,第一個(gè)方法帶有不斷向上查詢的過程,這個(gè)過程可能在一般情況下也不會太慢,不過我們最好將其記錄在一個(gè)地方,方便我們提供管理類來進(jìn)行直接管理,而不需要每次去獲取,對外調(diào)用都是封裝的運(yùn)行過程而已。

            好,回到話題,繼續(xù)說明JVM垃圾回收機(jī)制的信息,下面開始說明JAVA申請內(nèi)存、回收內(nèi)存的機(jī)制了。

            3JAVA如何申請內(nèi)存,和CC++有何區(qū)別。

               在上一次縮寫的關(guān)于JAVA集合類文章中其實(shí)已經(jīng)有部分說明,可以大致看到JAVA內(nèi)部是按照句柄指向?qū)嶓w的過程,不過這是從JAVA程序設(shè)計(jì)的角度去理解,如果我們需要更加細(xì)致的問一個(gè)問題是:JVM垃圾回收機(jī)制是如何知道哪些內(nèi)存是垃圾內(nèi)存的?JVM為什么不在平時(shí)就去回收內(nèi)存,而是要等到內(nèi)存不夠用的時(shí)候才會去回收內(nèi)存?不得不讓我進(jìn)一步去探討JAVA是如何細(xì)節(jié)的申請內(nèi)存的。

            從編程思想的角度來說,CC++new申請的內(nèi)存也是通過指針指向完成,不過你可以看成是一個(gè)地球板塊圖,在這些板塊中,他們?nèi)?/span>new的過程中,就是好比是找一個(gè)版塊,因?yàn)?/span>CC++在申請內(nèi)存的過程中,是不斷的freedelete操作,所以會產(chǎn)生很多內(nèi)存的碎片操作,而JAVA不是,JAVA只有內(nèi)存不夠用的時(shí)候才會去回收(回收細(xì)節(jié)講會在文章后面介紹),也就是說,可以保證內(nèi)存在一定程度上是連續(xù)的。從某種意義上將,只要下一塊申請的內(nèi)存不會到頭,就可以繼續(xù)在上一塊申請內(nèi)存的后面緊跟著去申請內(nèi)存,那么從某種意義上講,其申請的開銷可能可以和C++媲美。那么JAVA在回收內(nèi)存后,內(nèi)存還能是連續(xù)的嘛。。。。我們姑且這樣去理解,在第五節(jié)會說明。。繼續(xù)深入話題:

            在啟動weblogic的時(shí)候,如果打開任務(wù)管理器,可以馬上發(fā)現(xiàn),內(nèi)存被占用了最少-Xms的大小,一個(gè)說明現(xiàn)象就是JVM首先將內(nèi)存先占用了,然后再分配給其對象的,也就是說我們所謂的new可以理解為在堆上做了一個(gè)標(biāo)記,所以在一定程度上做連續(xù)分配內(nèi)存是可以實(shí)現(xiàn)的,只是你會發(fā)現(xiàn)若要真正實(shí)現(xiàn)連續(xù),必然導(dǎo)致一定程度上的序列化,所以new的開銷一般還是蠻大的,即使在后面說的JVM會將內(nèi)存分成幾個(gè)大塊來完成操作,但是也避免不了序列化的過程。

            在這里一個(gè)小推敲就是,一個(gè)SERVER的管理內(nèi)存范圍一般不要太大(一般在1~2G一個(gè)SERVER),推薦也不要太大,因數(shù)去考慮:

            1JAVA虛擬機(jī)回收內(nèi)存是在不夠用的時(shí)候再去回收,這個(gè)不夠用何以說明,很多時(shí)候因?yàn)橛?jì)算上的失誤導(dǎo)致內(nèi)存溢出。

            2、如果一個(gè)主機(jī)只有2G左右內(nèi)存,很少的CPU,那么一個(gè)JVM也好,但是如果主機(jī)很好,如32G內(nèi)存,那么這樣做未必有點(diǎn)過,第一發(fā)揮不出來,一個(gè)JVM管這么大塊內(nèi)存好像有點(diǎn)過,還有內(nèi)存不夠用去回收這么大塊內(nèi)存(回收內(nèi)存時(shí)一般需暫停服務(wù)),需要花時(shí)間,第二舉個(gè)很現(xiàn)實(shí)的例子,一個(gè)學(xué)校如果只有20~30人,一個(gè)人可以既當(dāng)校長又當(dāng)老師,如果一個(gè)學(xué)校有幾百上千人,我想這個(gè)人再大的能力忙死也管不過來,而且會出亂子,此時(shí)它要請班主任來管了。

            3、對于大內(nèi)存來說,使用多個(gè)SERVER完成負(fù)載均衡,一個(gè)暫停服務(wù)回收內(nèi)存,另一個(gè)還可以運(yùn)行嘛。

            4JVM如何尋找到需要回收的內(nèi)存:

            4.1 引用計(jì)數(shù)算法:在早期的JAVA虛擬機(jī)中,他們采用思維上最為常用的算法,就是計(jì)數(shù)器,在對象被使用“=”給與句柄的過程中(在集合類內(nèi)部雖然外部調(diào)用通過add或者put完成,不過內(nèi)部仍然是這樣),它同時(shí)會告訴JAVA虛擬機(jī),這個(gè)對象被增加一次引用(注意一個(gè)句柄如果已經(jīng)存在指向的實(shí)體,此時(shí)指向另一個(gè)實(shí)體,它也會同時(shí)告訴JAVA虛擬機(jī)以前指向那個(gè)實(shí)體少了一個(gè)引用,不然這就亂了),當(dāng)你使用句柄=null的時(shí)候,它會告訴JAVA虛擬機(jī)這個(gè)指向的實(shí)體少了一個(gè)引用,這個(gè)計(jì)數(shù)器并不是記錄在實(shí)體本省,而是被JVM私有化管理起來,這部分也是JVM垃圾回收機(jī)的基礎(chǔ)信息(私有化管理部分為JVM中的永久域,為所有靜態(tài)常量的管理池,如:public class的代碼段、static代碼段、static變量、String常量數(shù)組的一份拷貝等等),JVM回收內(nèi)存的時(shí)候,就會去找計(jì)數(shù)器中位0的元素,將其回收(回收過程,在第五節(jié)說明),如果有級聯(lián)引用的,如果父親級別的引用被回收后,子對象的引用數(shù)會自動減少1

            問題出來了:

            對于A對象有B對象的引用、B對象有A對象的引用,如果兩個(gè)引用都沒有程序員去手動去=null操作,那么實(shí)用引用計(jì)數(shù)算法,將永遠(yuǎn)計(jì)算不出來他們是需要被回收的內(nèi)存,這就是一種在使用引用計(jì)數(shù)器中的JAVA內(nèi)存泄露問題(對于這類內(nèi)存泄露屬于引用計(jì)數(shù)上,使用樹結(jié)構(gòu)遍歷是沒有問題的,同樣在引用計(jì)數(shù)算法上,存在多層集合類級聯(lián)引用的問題可能不會被回收到;另一類是流的內(nèi)存泄露(連接的內(nèi)存泄露也屬于其中),對于這類對象,進(jìn)行特殊的管理,內(nèi)部有一個(gè)管理器,如連接數(shù)據(jù)有一個(gè)DriverManager,他們永遠(yuǎn)都保存著對連接的引用,如果直接使用JDBC操作,使用完然后做close操作,相當(dāng)于告訴連接管理器斷開并可以釋放連接對象,反過來說,如果不做Close操作,系統(tǒng)永遠(yuǎn)不知道這塊資源信息是垃圾內(nèi)存,永遠(yuǎn)不會回收它,直到資源耗盡內(nèi)存溢出為止,直接將句柄=null,對于連接對象是無效)。

            這個(gè)引用計(jì)數(shù)需要遍歷整塊JVM才知道哪些需要回收,哪些不需要回收,太慢了。。。。

            4.2.樹結(jié)構(gòu)遍歷算法:其實(shí)為什么JAVA虛擬機(jī)可以管理其區(qū)域下的內(nèi)存,因?yàn)槲覀兪窃?/span>JVM內(nèi)部去創(chuàng)建內(nèi)存,所以可以理解為打一個(gè)TAG,那么它必然有一個(gè)根引用(靜態(tài)區(qū))通過分類的管理機(jī)制遍歷到所使用的每一個(gè)堆空間中,此時(shí)這棵很大的樹上,進(jìn)行遍歷下來得到的全部是活著的結(jié)點(diǎn)。那么沒有被遍歷到的全部是沒有活著的結(jié)點(diǎn),對于大量內(nèi)存需要回收的情況(很多情況下是的,因?yàn)闃I(yè)務(wù)級的請求用完這塊內(nèi)存就沒用了),我們很快可以知道哪些內(nèi)存是活著的,在后面回收時(shí)對應(yīng)其快速復(fù)制的過程,另一個(gè)區(qū)域就作為自動作為垃圾內(nèi)存了,在JDK1.4.2后開始逐步提出并行回收算法(包含了并行遍歷算法,內(nèi)部包含了后面說的需要將內(nèi)存分塊管理,而并非完全是一個(gè)整體)。

                 現(xiàn)在的JVM根據(jù)實(shí)際情況會采用一種自適應(yīng)的算法去尋找垃圾內(nèi)存,它會按照上述兩種算法進(jìn)行分別管理,當(dāng)發(fā)現(xiàn)樹結(jié)構(gòu)的開銷較大的時(shí)候(大部分是不需要回收的內(nèi)存,由于樹的遍歷要么通過遞歸消耗代碼段并在時(shí)間開銷上很大以外或者利用一個(gè)緩沖區(qū)來遍歷,比順序遍歷要慢很多,這種情況其實(shí)很少,如果真是這樣,內(nèi)存很容易溢出),所以此時(shí)它會自適應(yīng)的去采用引用計(jì)數(shù)算法去找需要回收的內(nèi)存部分。

             5JVM如何回收內(nèi)存的(回收算法分解詳述):

            首先了解幾個(gè)其他的概念:

            5.1.平時(shí)所說的JDK,其實(shí)是JAVA開發(fā)工具的意思,安裝JAVA虛擬機(jī)會產(chǎn)生兩個(gè)JRE目錄,JRE目錄為JAVA運(yùn)行時(shí)環(huán)境的意思,兩個(gè)JRE目錄的區(qū)別是其中在JDK所在的JRE目錄下沒有ServerClient文件夾(JDK1.5自動安裝包會自動將其復(fù)制到JDK下面一份),JRE為運(yùn)行時(shí)環(huán)境,提供對JVM操作的APIJVM內(nèi)部通過動態(tài)鏈接庫(就是配置PATH的路徑下),通過它作為主動態(tài)鏈接庫尋找到其它的動態(tài)鏈接庫,動態(tài)鏈接庫為何OS綁定的參數(shù),即代碼最終要通過這些東西轉(zhuǎn)換為X86指令集進(jìn)行運(yùn)行,另一個(gè)核心工具為JITJAVA即時(shí)編譯工具),用于將代碼轉(zhuǎn)換為對應(yīng)操作系統(tǒng)的運(yùn)行指令集合的過程,不過其與惰性評估形成對比,后面會專門介紹。

             5.1.JVM首先將大致分為JVM指令集、JVM存儲器、JVM內(nèi)存(堆棧區(qū)域部分)、JVM垃圾回收區(qū)域;JVM的堆部分又一般分為:新域、舊域、永久域(很多時(shí)候不會認(rèn)為段是堆的一部分,因?yàn)樗怯肋h(yuǎn)不會被回收的,它一般包含class的定義信息、static定義的方法、static匿名塊代碼段、常量信息(較為典型的就是String常量),不過這塊內(nèi)存也是可以被配置的);新域內(nèi)部又可以分為Eden和兩個(gè)救助區(qū)域,這幾個(gè)對象在JVM內(nèi)部有一定的默認(rèn)值,但是也是可以被設(shè)置的。

                當(dāng)新申請的對象的時(shí)候,會放入Eden區(qū)中(這個(gè)區(qū)域一般不會太大),當(dāng)對象在一定時(shí)間內(nèi)還在使用的時(shí)候,它會逐步的進(jìn)入舊域(此時(shí)是一個(gè)內(nèi)存復(fù)制的過程,舊區(qū)域按照順序,其引用的句柄也會被修改指向的位置),JVM回收中會先將Eden里面的內(nèi)存和一個(gè)救助區(qū)域的內(nèi)存就會被賦值到另一個(gè)救助區(qū)域,然后對這兩塊內(nèi)存進(jìn)行回收,同理,舊區(qū)域也有一個(gè)差不多大小的內(nèi)存區(qū)域進(jìn)行被復(fù)制,這個(gè)復(fù)制的過程肯定就會在一定程度上將內(nèi)存連續(xù)的排列起來;另外可以想到JAVA提供內(nèi)存復(fù)制最快的就是System.arrayCopy方法,那么這個(gè)肯定是按照內(nèi)存數(shù)組進(jìn)行拷貝(JVM起始就是一個(gè)大內(nèi)存,本身就可以成是幾個(gè)大數(shù)組組成的,而這個(gè)拷貝方法,默認(rèn)拷貝多長呢,其實(shí)數(shù)組最長可以達(dá)到多少,通過數(shù)組的length返回的是int類型數(shù)據(jù)就可以清楚發(fā)現(xiàn),為int類型的上限1<<30的長度(理想情況,因?yàn)橛锌赡芤驗(yàn)椴僮飨到y(tǒng)的其他進(jìn)程導(dǎo)致JVM內(nèi)存本身就不是連續(xù)的),即在2G*單元內(nèi)存長度,所以也在一定程度上說明我們的一個(gè)JVM設(shè)置內(nèi)存不要太大,不然復(fù)制內(nèi)存的過程開銷是很大的)。

                其實(shí)上述描述的是一種停止-復(fù)制回收算法,在這個(gè)過程中形成了幾個(gè)大的內(nèi)存來回倒,這必然是很惡心的事情,那么繼續(xù)將其切片為幾個(gè)大的板塊,有些大的對象會出現(xiàn)一兩個(gè)對象占用一個(gè)版塊的現(xiàn)象,這些大對象基本不會怎么移動(被回收就是另一回事,因?yàn)闀蹇者@個(gè)版塊),板塊之間有一些對應(yīng)關(guān)系,在回收時(shí)先將一些版塊的小對象,向另一個(gè)還未裝滿的大板塊內(nèi)部轉(zhuǎn)移,復(fù)制的粒度變小了,另外管理上可以發(fā)揮多線程的優(yōu)勢所在,好比是將一塊大的田地,分成很多小田地,每塊田地種植不同檔次的秧苗,將其劃分等級,我們假如秧苗經(jīng)常會隨機(jī)的死掉一些(這塊是垃圾),在清理一些很普通的秧苗田地的時(shí)候,可能會將其中一塊或幾塊田地的(活著的秧苗)種植到另一塊田地中,但是他們不可以將高檔次的秧苗移植到低檔次的田地中,因?yàn)楦邫n次的秧苗造價(jià)太高(內(nèi)存太大),移植過程中代價(jià)太大,需要使用非普通秧苗的手段要移動他們,所以基本不移動他們,除非豐收他們的時(shí)候(他們也成為垃圾內(nèi)存的時(shí)候),才會被拔出,騰出田地來。在轉(zhuǎn)移秧苗的過程中,他們需要整理出順序便于管理,在很多書籍上把這個(gè)過程叫做壓縮,因?yàn)檫@樣使得保證在只要內(nèi)存不溢出的情況下,申請的對象都有足夠的控件可以存放,不然零碎的空間中間的縫隙未必可以存放下一個(gè)較大的對象。將內(nèi)存分塊管理就是另一個(gè)停止復(fù)制收集器的進(jìn)一步升級:增量收集思想。

             5.2.回收過程,垃圾回收機(jī)制一般分為:標(biāo)記清除、標(biāo)記壓縮、停止復(fù)制、增量收集、分代收集、并發(fā)收集和并行收集。

             標(biāo)記清除收集器,一般依賴于第一種尋找垃圾的算法去尋找,當(dāng)尋找到需要使用的內(nèi)存,會打一個(gè)TAG,此時(shí)未標(biāo)記的對象,就會被該收集器回收,一般先停止外部服務(wù),并且是單線程的去尋找并清除。

             標(biāo)記壓縮收集器,和上述收集器唯一區(qū)別就是多一步壓縮操作,壓縮操作刪除前或刪除后去操作是將標(biāo)記為正在使用的對象復(fù)制到一塊新的內(nèi)存中,也就是大致上是按照順序排列的。

             停止復(fù)制收集器、增量收集器上述描述的較多,這里就不再多說了,總之是來回倒這些內(nèi)存,為了介于內(nèi)存,在其基礎(chǔ)上按照一定規(guī)則進(jìn)行內(nèi)存分塊操作。

             并發(fā)收集器,這里一定要明確并發(fā)和并行的概念不同之處,并發(fā)收集器是可以再內(nèi)存回收的過程中不暫停服務(wù),也就是不影響運(yùn)行,類似上述的收集器,要進(jìn)行壓縮空間等操作不得不暫停服務(wù)保證系統(tǒng)的正常運(yùn)行;可以思考到它基本會建立在首先將內(nèi)存分塊的基礎(chǔ)上,可能會更細(xì),它獨(dú)立的運(yùn)行并和應(yīng)用程序同時(shí)運(yùn)行,回收的方式也是和上面一樣,只是它復(fù)制的時(shí)候只是較小部分內(nèi)存的復(fù)制,所以其他業(yè)務(wù)系統(tǒng)運(yùn)行照常,粒度很小,最好的情況就是這塊內(nèi)存99%是需要回收的,它正在回收這塊內(nèi)存,關(guān)于剩下的1%的內(nèi)存應(yīng)用服務(wù)被暫停,其余其它塊的內(nèi)存照常運(yùn)行不受到影響。

             并行收集器是使用上述某類收集方法,但是使用多線程算法進(jìn)行回收,在服務(wù)器應(yīng)用中,利用多CPU進(jìn)行回收可以顯著提高性能,此時(shí)需要進(jìn)行相關(guān)的配置,在第六節(jié)中有詳細(xì)的說明。

             對于上述了解后,可能對于不同的應(yīng)用服務(wù)器有不同的JVM垃圾查找算法和回收算法,但是大致不離其中,根據(jù)實(shí)際情況,進(jìn)行服務(wù)器的相關(guān)調(diào)試就可以在一定程度上提高服務(wù)器的運(yùn)行性能,第六節(jié)就詳細(xì)說明下。

            6、應(yīng)用服務(wù)器部署及常用參數(shù)設(shè)置:

            說到JVM的配置,最常用的兩個(gè)配置就是:

            -Xms512m –Xmx1024m

            -Xms設(shè)置JVM的初始化內(nèi)存大小,-Xmx為最大內(nèi)存大小,當(dāng)突破這個(gè)值,將會報(bào)內(nèi)存溢出,導(dǎo)致的原因有很多,主要是虛擬機(jī)的回收問題以及程序設(shè)計(jì)上的內(nèi)存泄露問題;由于在超過-Xms時(shí)會產(chǎn)生頁面申請的開銷,所以一般很多應(yīng)用服務(wù)器會推薦-Xms-Xmx是等值的;最大值一般不保持在主機(jī)內(nèi)存的75%的內(nèi)存左右(多個(gè)SERVER是加起來的內(nèi)存),當(dāng)JVM絕大部分時(shí)間處于回收狀態(tài),并且內(nèi)存長時(shí)間處于非常長少的狀態(tài)就會報(bào):java.lang.OutOfMemoryError:Java heap space的錯誤。

             上面提及到JVM很多的知識面,很顯然你想去設(shè)置一下其它的參數(shù),其實(shí)對于JVM設(shè)置的參數(shù)有上百個(gè),這里就說一些較為常用配置即可。

             JVM內(nèi)存配置分兩大類:

            1-X開頭的參數(shù)信息:一般每個(gè)版本變化不大。

            2-XX開頭的參數(shù)信息:版本升級變化較大,如果沒有太大必要保持默認(rèn)即可。

            3、另外還有一個(gè)特殊的選項(xiàng)就是-server還是-client,他們在默認(rèn)配置內(nèi)存上有一些細(xì)微的區(qū)別,直接用JDK運(yùn)行程序默認(rèn)是-client,應(yīng)用服務(wù)器生產(chǎn)模式一般只會用-server

             這些命令其實(shí)就是在運(yùn)行java命令或者javaw等相關(guān)命令后可以配置的參數(shù),如果不配置,他們有相應(yīng)的默認(rèn)值配置。

             1-X開頭的常用配置信息:

            -Xnoclassgc  禁用垃圾回收,一般不適用這個(gè)參數(shù)

            -Xincgc       啟用增量垃圾回收

            -Xmn1024K   Eden區(qū)初始化JAVA堆的尺寸,默認(rèn)值640K

            -Xms512m    JAVA堆初始化尺寸,默認(rèn)是32M

            -Xmx512m    JAVA堆最大尺寸,默認(rèn)64M,一般不超過2G,在64位機(jī)上,使用64位的JVM,需要操作系統(tǒng)進(jìn)行unlimited方可設(shè)置到2G以上。

             2-XX開頭常用內(nèi)存配置信息:

            -XX:-DisableExplicitGC  將會忽略手動調(diào)用GC的代碼,如:System.gc(),將-DisableExplicitGC   改成+DisableExplicitGC即為啟用,默認(rèn)為啟用,什么也不寫,默認(rèn)是加號,但是系統(tǒng)內(nèi)部默認(rèn)的并不是什么都啟用。

            -XX:+UseParallelGC     將會自動啟用并行回收,多余多CPU主機(jī)有效,默認(rèn)是不啟用。

            -XX:+UseParNewGC     啟用并行收集(不是回收),也是多CPU有效。

            -XX:NewSize=128m     新域的初始化尺寸。

            -XX:MaxNewSize=128m 新創(chuàng)建的對象都是在Eden中,其屬于新域,在-client中默認(rèn)為640K,而-server中默認(rèn)是2M,為減少頻繁的對新域進(jìn)行回收,可以適當(dāng)調(diào)大這個(gè)值。

            -XX:PerSize=64m        設(shè)置永久域的初始化大小,在WEBLOGIC中默認(rèn)的尺寸應(yīng)該是48M,一般夠用,可以根據(jù)實(shí)際情況作相應(yīng)條調(diào)整。

            -XX:MaxPerSize=64m    設(shè)置永久域的最大尺寸。

            另外還可以設(shè)置按照區(qū)域的比例進(jìn)行設(shè)置操作,以及設(shè)置線程、緩存、頁面大小等等操作。

             3-XX開頭的幾個(gè)監(jiān)控信息:

            -XX:+GITime              顯示有多少時(shí)間花在編譯代碼代碼上,這部分為運(yùn)行時(shí)編譯為對應(yīng)機(jī)器碼時(shí)間。

            -XX:+PrintGC              打印垃圾回收的基本信息

            -XX:+PrintGCTimeStamps 打印垃圾回收時(shí)間戳信息

            -XX:+PrintGCDetails       打印垃圾回收的詳細(xì)信息

            -XX:+TraceClassLoading    跟蹤類的加載

            -XX:+TraceClassResolution 跟蹤常量池

            -XX:+TraceClassUnLoading 跟蹤類卸載

            等等。。。。。。

             例子:

            編寫一個(gè)簡單的JAVA類:

            public class Hello {

                 public static void main(String []args) {

                     byte []a1 = new byte[4*1024*1024];

                     System.out.println("第一次申請");

                     byte []a2 = new byte[4*1024*1024];

                     System.out.println("第二次申請");

                     byte []a3 = new byte[4*1024*1024];

                     System.out.println("第三次申請");

                     byte []a4 = new byte[4*1024*1024];

                     System.out.println("第四次申請");

                     byte []a5 = new byte[4*1024*1024];

                     System.out.println("第五次申請");

                     byte []a6 = new byte[4*1024*1024];

                     System.out.println("第六次申請");

                 }

            }

            此時(shí)運(yùn)行程序,這樣調(diào)試一下:

            C:\>java -Xmn4m -Xms16m -Xmx16m Hello

            第一次申請

            第二次申請

            Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

             此時(shí)內(nèi)存溢出,因?yàn)檫@些空間都有釋放,16M空間正好一半8M就溢出了,和我們的理論較吻合的就是一半在使用一半的大小就會崩掉,顯示的使用值會成倍增加。

             那么我們將程序修改一下再看效果

            public class Hello {

                 public static void main(String []args) {

                     byte []a1 = new byte[4*1024*1024];

                     System.out.println("第一次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第二次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第三次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第四次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第五次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第六次申請");

                 }

            }

            運(yùn)行程序如下:

            C:\>javac Hello.java

             C:\>java -Xmn4m -Xms16m -Xmx16m Hello

            第一次申請

            第二次申請

            第三次申請

            第四次申請

            第五次申請

            第六次申請

            程序正常下來了,說明中途進(jìn)行了垃圾回收的動作,我們想看下垃圾回收的整個(gè)過程,如何看,把上面的參數(shù)搬下來:

            E:\>java -Xmn4m -Xms16m -Xmx16m -XX:+PrintGCDetails Hello

            第一次申請

            第二次申請

            [GC [DefNew: 189K->133K(3712K), 0.0014622 secs][Tenured: 8192K->4229K(12288K), 0.0089967 secs] 8381K->4229K(16000K), 0.0110011 secs]

            第三次申請

            [GC [DefNew: 0K->0K(3712K), 0.0004749 secs][Tenured: 8325K->4229K(12288K), 0.0083114 secs] 8325K->4229K(16000K), 0.0092936 secs]

            第四次申請

            [GC [DefNew: 0K->0K(3712K), 0.0003168 secs][Tenured: 8325K->4229K(12288K), 0.0081516 secs] 8325K->4229K(16000K), 0.0089735 secs]

            第五次申請

            [GC [DefNew: 0K->0K(3712K), 0.0003179 secs][Tenured: 8325K->4229K(12288K), 0.0080368 secs] 8325K->4229K(16000K), 0.0088335 secs]

            第六次申請

             從上面的結(jié)果中看到第一次回收是從兩個(gè)對象申請后,開始回收,后面是每申請一個(gè)就回收一次,那是因?yàn)椋绦虼a中始終用一個(gè)句柄指向了一個(gè)空間,第一次回收的時(shí)候只回收了一個(gè)對象,還有一個(gè)對象沒有回收,而當(dāng)后面申請對象過程中,沒申請一個(gè)對象以前那個(gè)對象就是垃圾,而且內(nèi)存又滿了,所以內(nèi)存就會在每申請一個(gè)對象的時(shí)候被回收。

             把程序稍微修改一下,增加兩個(gè)申請得較大的內(nèi)存,因?yàn)樾聟^(qū)域的回收從上一個(gè)試驗(yàn)中看不出來:

            public class Hello {

                 public static void main(String []args) {

                     byte []a1 = new byte[4*1024*1024];

                     System.out.println("第一次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第二次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第三次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第四次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第五次申請");

                     a1 = new byte[4*1024*1024];

                     System.out.println("第六次申請");

                     a1 = new byte[12*1024*1024];

                     System.out.println("第七次申請(大三倍的內(nèi)存)");

                     a1 = new byte[12*1024*1024];

                     System.out.println("第八次申請(大三倍的內(nèi)存)");

                 }

            }

            E:\>javac Hello.java

            --先將新區(qū)域設(shè)置為4m看下結(jié)果:

            E:\>java -Xmn4m -Xms32m -Xmx32m -XX:+PrintGCDetails Hello

            第一次申請

            第二次申請

            第三次申請

            第四次申請

            第五次申請

            第六次申請

            [GC [DefNew: 189K->133K(3712K), 0.0015527 secs][Tenured: 24576K->4229K(28672K), 0.0091076 secs] 24765K->4229K(32384K), 0.0111978 secs]

            第七次申請(大三倍的內(nèi)存)

            [GC [DefNew: 0K->0K(3712K), 0.0005428 secs][Tenured: 16517K->12421K(28672K), 0.0131774 secs] 16517K->12421K(32384K), 0.0142610 secs]

            第八次申請(大三倍的內(nèi)存)

             --再將新域的大小擴(kuò)大一倍:

            E:\>java -Xmn8m -Xms32m -Xmx32m -XX:+PrintGCDetails Hello

            第一次申請

            [GC [DefNew: 4362K->133K(7424K), 0.0049861 secs] 4362K->4229K(32000K), 0.0053091 secs]

            第二次申請

            [GC [DefNew: 4229K->133K(7424K), 0.0043679 secs] 8325K->8325K(32000K), 0.0047056 secs]

            第三次申請

            [GC [DefNew: 4229K->133K(7424K), 0.0044629 secs] 12421K->12421K(32000K), 0.0047791 secs]

            第四次申請

            [GC [DefNew: 4229K->133K(7424K), 0.0044263 secs] 16517K->16517K(32000K), 0.0047646 secs]

            第五次申請

            [GC [DefNew: 4229K->133K(7424K), 0.0045198 secs] 20613K->20613K(32000K), 0.0048372 secs]

            第六次申請

            [GC [DefNew: 4229K->4229K(7424K), 0.0001604 secs][Tenured: 20480K->4229K(24576K), 0.0092190 secs] 24709K->4229K(32000K), 0.0098943 secs]

            第七次申請(大三倍的內(nèi)存)

            [GC [DefNew: 0K->0K(7424K), 0.0005219 secs][Tenured: 16517K->12421K(24576K), 0.0131838 secs] 16517K->12421K(32000K), 0.0142096 secs]

            [Full GC [Tenured: 12421K->12420K(24576K), 0.0115076 secs] 12421K->12420K(32000K), [Perm : 338K->337K(8192K)], 0.0123320 secs]

            Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

               盡然內(nèi)存溢出了,怎么擴(kuò)大了內(nèi)存溢出了?難道新域不能設(shè)置得太大?那么為了論證,我們將新域的大小再放大一倍:

            E:\>java -Xmn16m -Xms32m -Xmx32m -XX:+PrintGCDetails Hello

            第一次申請

            第二次申請

            第三次申請

            [GC [DefNew: 12551K->133K(14784K), 0.0052593 secs] 12551K->4229K(31168K), 0.0055915 secs]

            第四次申請

            第五次申請

            第六次申請

            [GC [DefNew: 12556K->12556K(14784K), 0.0001953 secs][Tenured: 4096K->4229K(16384K), 0.0091847 secs] 16652K->4229K(31168K), 0.0098906 secs]

            第七次申請(大三倍的內(nèi)存)

            [GC [DefNew: 12288K->12288K(14784K), 0.0001972 secs][Tenured: 4229K->12421K(16384K), 0.0148896 secs] 16517K->12421K(31168K), 0.0155777 secs]

            第八次申請(大三倍的內(nèi)存)

             為什么又不內(nèi)存溢出了,所以這個(gè)設(shè)置和絕對是否大和絕對是否小幾乎無關(guān),而是和實(shí)際情況的對象的關(guān)系,我們推算一下:

             前面每次理想申請堆空間是4M(新域內(nèi)部還包含救助區(qū)域,所以直接放不下了),這個(gè)申請直接就進(jìn)入舊域才能放下,所以在新區(qū)域申請4m的過程中,舊區(qū)域空間又28m空間,前6次申請空間的時(shí)候,是4*6=24M接近位數(shù),此時(shí)第七次申請空間時(shí)候就需要回收內(nèi)存,可以看到回收的全部是舊域的內(nèi)存,新域的內(nèi)存沒有什么變化。

             而當(dāng)新域設(shè)置為8M的時(shí)候,舊域只有24M,第一個(gè)4M顯然能放下去,第二個(gè)4M不行了,就要將前面那個(gè)轉(zhuǎn)移到舊域內(nèi)部,這個(gè)轉(zhuǎn)移過程并不會去做GC的操作,而是不斷的將信息轉(zhuǎn)移過去,而這些內(nèi)存屬于垃圾,后面會不斷從新域向舊區(qū)域轉(zhuǎn)移數(shù)據(jù),但是這個(gè)過程中不會回收信息,和上述一樣,不過當(dāng)一個(gè)12M的數(shù)據(jù)下來的時(shí)候,此時(shí)前面會有最多兩個(gè)對象及8M的內(nèi)容在新區(qū)域內(nèi)部(但是通過試驗(yàn)論證最多只能又一個(gè)在新域,因?yàn)樾掠螂m然有8M但是實(shí)際拿給Eden用的肯定沒有這么多),此時(shí)也就是說舊域使用的內(nèi)存應(yīng)該是16M或者20M的空間,但是此時(shí)JVM認(rèn)為還沒有到必須要回收內(nèi)存的地步,如果進(jìn)來一個(gè)4M以內(nèi)的內(nèi)存,JVM啟動內(nèi)存馬上回收,即可將垃圾內(nèi)存回收掉;而進(jìn)來一個(gè)12M的內(nèi)存,整個(gè)JVM新域+舊區(qū)域也放不下那么大的內(nèi)存了,所以會報(bào)內(nèi)存溢出。

            同理可以分析設(shè)置為16M的情況。

             在設(shè)置過程中并不是設(shè)置大或者小有好處,根據(jù)實(shí)際情況而定如何去控制,默認(rèn)情況下,新域的大小為舊域的二分之一(即堆空間的三分之一),由參數(shù):-XX:NewRatio的值來設(shè)置其比例關(guān)系,默認(rèn)值為2

             7、擴(kuò)展話題JIT(及時(shí)編譯技術(shù))與lazy evaluation(惰性評估):

            posted on 2012-04-11 14:06 Snape 閱讀(424) 評論(0)  編輯 收藏 引用 所屬分類: Java

            導(dǎo)航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統(tǒng)計(jì)

            常用鏈接

            留言簿

            隨筆分類

            隨筆檔案

            文章分類

            文章檔案

            my

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            国产精品99久久免费观看| 国产香蕉久久精品综合网| 中文字幕无码精品亚洲资源网久久| 色综合久久精品中文字幕首页 | 97久久超碰国产精品旧版| 久久综合久久综合亚洲| 久久精品亚洲欧美日韩久久| 亚洲国产精久久久久久久| 久久精品一区二区| 99久久99久久精品国产| 久久国产热这里只有精品| 久久精品成人| 97视频久久久| 欧洲精品久久久av无码电影 | 一本大道久久香蕉成人网 | 69久久精品无码一区二区| 久久丫精品国产亚洲av| 99精品国产在热久久无毒不卡 | 国内精品伊人久久久久AV影院| 亚洲色大成网站www久久九| 成人午夜精品无码区久久| av无码久久久久不卡免费网站| 久久国产精品77777| 青青草国产精品久久久久| 久久99精品国产麻豆婷婷| 青青热久久国产久精品 | 亚洲国产精品一区二区久久hs| 国产亚洲美女精品久久久2020| 99久久免费国产特黄| 久久99精品国产麻豆婷婷| 99久久夜色精品国产网站| 9191精品国产免费久久| 亚洲综合久久久| 久久精品国产亚洲综合色| 人人狠狠综合88综合久久| 色综合久久久久综合体桃花网| 一本久久a久久精品综合夜夜| 伊色综合久久之综合久久| 四虎国产精品免费久久久| 久久亚洲国产最新网站| 国产精品美女久久久久AV福利|