<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    走在架構師的大道上 Jack.Wang's home

    Java, C++, linux c, C#.net 技術,軟件架構,領域建模,IT 項目管理 Dict.CN 在線詞典, 英語學習, 在線翻譯

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      195 Posts :: 3 Stories :: 728 Comments :: 0 Trackbacks

    有關 java 內存方面的教程 請看 http://www.tudou.com/programs/view/hN_4sQJMoFQ/

    今天看了一下java的內存分配,分享一下:
    基礎數據類型直接在棧空間分配, 方法的形式參數,直接在??臻g分配,當方法調用完成后從棧空間回收。  引用數據類型,需要用new來創建,既在??臻g分配一個地址空間,又在堆空間分配對象的類變量 。 方法的引用參數,在??臻g分配一個地址空間,并指向堆空間的對象區,當方法調用完成后從??臻g回收。局部變量 new 出來時,在??臻g和堆空間中分配空間,當局部變量生命周期結束后,棧空間立刻被回收,堆空間區域等待GC回收。 方法調用時傳入的 literal 參數,先在??臻g分配,在方法調用完成后從棧空間分配。字符串常量在 DATA 區域分配 ,this 在堆空間分配 。數組既在??臻g分配數組名稱, 又在堆空間分配數組實際的大??!
    哦 對了,補充一下static在DATA區域分配。
    其實是有規律的,只要你理解了這些個基本的原理:
    堆空間的話: 操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。另外由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

    棧空間的話:在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

    ok,頭會不會有點小暈,不會的話繼續吧:
    JVM中的堆和棧
    JVM是基于堆棧的虛擬機.JVM為每個新創建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
    我們知道,某個線程正在執行的方法稱為此線程的當前方法.我們可能不知道,當前方法使用的幀稱為當前幀。當線程激活一個Java方法,JVM就會在線程的Java堆棧里新壓入一個幀。這個幀自然成為了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變量,中間計算過程和其他數據.這個幀在這里和編譯原理中的活動紀錄的概念是差不多的.
    從Java的這種分配機制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統在建立某個進程時或者線程(在支持多線程的操作系統中是線程)為這個線程建立的存儲區域,該區域具有先進后出的特性。
    每一個Java應用都唯一對應一個JVM實例,每一個實例唯一對應一個堆。應用程序在運行中所創建的所有類實例或數組都放在這個堆中,并由應用所有的線程共享.跟C/C++不同,Java中分配堆內存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時從兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。

    PS: 相關內容

    方法區
    在一個jvm實例的內部,類型信息被存儲在一個稱為方法區的內存邏輯區中。類型信息是由類加載器在類加載時從類文件中提取出來的。類(靜態)變量也存儲在方法區中。

    jvm實現的設計者決定了類型信息的內部表現形式。如,多字節變量在類文件是以big-endian存儲的,但在加載到方法區后,其存放形式由jvm根據不同的平臺來具體定義。

    jvm在運行應用時要大量使用存儲在方法區中的類型信息。在類型信息的表示上,設計者除了要盡可能提高應用的運行效率外,還要考慮空間問題。根據不同的需求,jvm的實現者可以在時間和空間上追求一種平衡。

    因為方法區是被所有線程共享的,所以必須考慮數據的線程安全。假如兩個線程都在試圖找lava的類,在lava類還沒有被加載的情況下,只應該有一個線程去加載,而另一個線程等待。

    方法區的大小不必是固定的,jvm可以根據應用的需要動態調整。同樣方法區也不必是連續的。方法區可以在堆(甚至是虛擬機自己的堆)中分配。jvm可以允許用戶和程序指定方法區的初始大小,最小和最大尺寸。

    方法區同樣存在垃圾收集,因為通過用戶定義的類加載器可以動態擴展java程序,一些類也會成為垃圾。jvm可以回收一個未被引用類所占的空間,以使方法區的空間最小。

    類型信息
    對每個加載的類型,jvm必須在方法區中存儲以下類型信息:
    一 這個類型的完整有效名
    二 這個類型直接父類的完整有效名(除非這個類型是interface或是
        java.lang.Object,兩種情況下都沒有父類)
    三 這個類型的修飾符(public,abstract, final的某個子集)
    四 這個類型直接接口的一個有序列表

    類型名稱在java文件和jvm中都以完整有效名出現。在java源代碼中,完整有效名由類的所屬包名稱加一個".",再加上類名
    組成。例如,類Object的所屬包為java.lang,那它的完整名稱為java.lang.Object,但在類文件里,所有的"."都被
    斜杠“/”代替,就成為java/lang/Object。完整有效名在方法區中的表示根據不同的實現而不同。

    除了以上的基本信息外,jvm還要為每個類型保存以下信息:
     類型的常量池( constant pool)
     域(Field)信息
     方法(Method)信息
     除了常量外的所有靜態(static)變量

    常量池
    jvm為每個已加載的類型都維護一個常量池。常量池就是這個類型用到的常量的一個有序集合,包括實際的常量(string,
    integer, 和floating point常量)和對類型,域和方法的符號引用。池中的數據項象數組項一樣,是通過索引訪問的。
    因為常量池存儲了一個類型所使用到的所有類型,域和方法的符號引用,所以它在java程序的動態鏈接中起了核心的作用。

    域信息
    jvm必須在方法區中保存類型的所有域的相關信息以及域的聲明順序,
    域的相關信息包括:
    域名
    域類型
    域修飾符(public, private, protected,static,final   volatile, transient的某個子集)
           
    方法信息
    jvm必須保存所有方法的以下信息,同樣域信息一樣包括聲明順序
    方法名
    方法的返回類型(或 void)
    方法參數的數量和類型(有序的)
    方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個子集)除了abstract和native方法外,其他方法還有保存方法的字節碼(bytecodes)操作數棧和方法棧幀的局部變量區的大小           
    異常表

    類變量(
      Class Variables
      譯者:就是類的靜態變量,它只與類相關,所以稱為類變量
    )
    類變量被類的所有實例共享,即使沒有類實例時你也可以訪問它。這些變量只與類相關,所以在方法區中,它們成為類數據在邏輯上的一部分。在jvm使用一個類之前,它必須在方法區中為每個non-final類變量分配空間。

    常量(被聲明為final的類變量)的處理方法則不同,每個常量都會在常量池中有一個拷貝。non-final類變量被存儲在聲明它的
    類信息內,而final類被存儲在所有使用它的類信息內。

    對類加載器的引用
    jvm必須知道一個類型是由啟動加載器加載的還是由用戶類加載器加載的。如果一個類型是由用戶類加載器加載的,那么jvm會將這個類加載器的一個引用作為類型信息的一部分保存在方法區中。

    jvm在動態鏈接的時候需要這個信息。當解析一個類型到另一個類型的引用的時候,jvm需要保證這兩個類型的類加載器是相同的。這對jvm區分名字空間的方式是至關重要的。

    對Class類的引用
    jvm為每個加載的類型(譯者:包括類和接口)都創建一個java.lang.Class的實例。而jvm必須以某種方式把Class的這個實例和存儲在方法區中的類型數據聯系起來。

    你可以通過Class類的一個靜態方法得到這個實例的引用// A method declared in class java.lang.Class:
    public static Class forName(String className);

    假如你調用forName("java.lang.Object"),你會得到與java.lang.Object對應的類對象。你甚至可以通過這個函數
    得到任何包中的任何已加載的類引用,只要這個類能夠被加載到當前的名字空間。如果jvm不能把類加載到當前名字空間,
    forName就會拋出ClassNotFoundException。
    (譯者:熟悉COM的朋友一定會想到,在COM中也有一個稱為      類對象(Class Object)的東東,這個類對象主要      是實現一種工廠模式,而java由于有了jvm這個中間      層,類對象可以很方便的提供更多的信息。這兩種類對象      都是Singleton的)

    也可以通過任一對象的getClass()函數得到類對象的引用,getClass被聲明在Object類中:
    // A method declared in class java.lang.Object:
    public final Class getClass();
    例如,假如你有一個java.lang.Integer的對象引用,可以激活getClass()得到對應的類引用。

    通過類對象的引用,你可以在運行中獲得相應類存儲在方法區中的類型信息,下面是一些Class類提供的方法:
    // Some of the methods declared in class java.lang.Class:
    public String getName();
    public Class getSuperClass();
    public boolean isInterface();
    public Class[] getInterfaces();
    public ClassLoader getClassLoader();

    這些方法僅能返回已加載類的信息。getName()返回類的完整名,getSuperClass()返回父類的類對象,isInterface()判斷是否是接口。getInterfaces()返回一組類對象,每個類對象對應一個直接父接口。如果沒有,則返回一個長度為零的數組。
    getClassLoader()返回類加載器的引用,如果是由啟動類加載器加載的則返回null。所有的這些信息都直接從方法區中獲得。

    方法表
    為了提高訪問效率,必須仔細的設計存儲在方法區中的數據信息結構。除了以上討論的結構,jvm的實現者還可以添加一些其他的數據結構,如方法表。jvm對每個加載的非虛擬類的類型信息中都添加了一個方法表,方法表是一組對類實例方法的直接引用(包括從父類繼承的方法)。jvm可以通過方法表快速激活實例方法。(譯者:這里的方法表與C++中的虛擬函數表一樣,但java方法全都 是virtual的,自然也不用虛擬二字了。正像java宣稱沒有      指針了,其實java里全是指針。更安全只是加了更完備的檢查機制,但這都是以犧牲效率為代價的,個人認為java的設計者      始終是把安全放在效率之上的,所有java才更適合于網絡開發)

    一個例子
    為了顯示jvm如何使用方法區中的信息,我們據一個例子,我們
    看下面這個類:
    class Lava {
        private int speed = 5; // 5 kilometers per hour
        void flow() {
        }
    }

    class Volcano {
        public static void main(String[] args) {
            Lava lava = new Lava();
            lava.flow();
        }
    }
    下面我們描述一下main()方法的第一條指令的字節碼是如何被執行的。不同的jvm實現的差別很大,這里只是其中之一。

    為了運行這個程序,你以某種方式把“Volcano"傳給了jvm。有了這個名字,jvm找到了這個類文件(Volcano.class)并讀入,它從
    文件提取了類型信息并放在了方法區中,通過解析存在方法區中的字節碼,jvm激活了main()方法,在執行時,jvm保持了一個指向當前類(Volcano)常量池的指針。

    注意jvm在還沒有加載Lava類的時候就已經開始執行了。正像大多數的jvm一樣,不會等所有類都加載了以后才開始執行,它只會在需要的時候才加載。

    main()的第一條指令告知jvm為列在常量池第一項的類分配足夠的內存。jvm使用指向Volcano常量池的指針找到第一項,發現是一個對Lava類的符號引用,然后它就檢查方法區看lava是否已經被加載了。

    這個符號引用僅僅是類lava的完整有效名”lava“。這里我們看到為了jvm能盡快從一個名稱找到一個類,一個良好的數據結構是多么重要。這里jvm的實現者可以采用各種方法,如hash表,查找樹等等。同樣的算法可以用于Class類的forName()的實現。

    當jvm發現還沒有加載過一個稱為"Lava"的類,它就開始查找并加載類文件"Lava.class"。它從類文件中抽取類型信息并放在了方法區中。

    jvm于是以一個直接指向方法區lava類的指針替換了常量池第一項的符號引用。以后就可以用這個指針快速的找到lava類了。而這個替換過程稱為常量池解析(constant pool resolution)。在這里我們替換的是一個native指針。

    jvm終于開始為新的lava對象分配空間了。這次,jvm仍然需要方法區中的信息。它使用指向lava數據的指針(剛才指向volcano常量池第一項的指針)找到一個lava對象究竟需要多少空間。

    jvm總能夠從存儲在方法區中的類型信息知道某類型對象需要的空間。但一個對象在不同的jvm中可能需要不同的空間,而且它的空間分布也是不同的。(譯者:這與在C++中,不同的編譯器也有不同的對象模型是一個道理)

    一旦jvm知道了一個Lava對象所要的空間,它就在堆上分配這個空間并把這個實例的變量speed初始化為缺省值0。假如lava的父對象也有實例變量,則也會初始化。

    當把新生成的lava對象的引用壓到棧中,第一條指令也結束了。下面的指令利用這個引用激活java代碼把speed變量設為初始值,5。另外一條指令會用這個引用激活Lava對象的flow()方法。





    本博客為學習交流用,凡未注明引用的均為本人作品,轉載請注明出處,如有版權問題請及時通知。由于博客時間倉促,錯誤之處敬請諒解,有任何意見可給我留言,愿共同學習進步。
    posted on 2008-05-21 21:14 Jack.Wang 閱讀(16367) 評論(9)  編輯  收藏 所屬分類: 開發技術

    Feedback

    # re: java內存分配 2008-05-21 21:22 麥田守望者
    學習  回復  更多評論
      

    # re: java內存分配 2008-05-22 08:22 如坐春風
    不錯,希望看到這方面的更多文章。  回復  更多評論
      

    # re: java內存分配 2008-05-23 09:07 yeshucheng
    希望更多看到類似文章,不過還可以深入結合深入JAVA虛擬機的很多方面來詳盡說明  回復  更多評論
      

    # re: java內存分配[未登錄] 2008-06-11 22:43 菜鳥
    我想請問一下,對象中的屬性字段是基本數據類型,他的值是放在那里的呢
      回復  更多評論
      

    # re: java內存分配 2008-06-12 17:06 Jack.Wang
    可能每個 JVM 的實現有所差異,但一般對于基本數據類型,要看你是怎么聲明的,如果 main(String args[]){int a = 50; }這樣的話就是在棧里。如果有個 Class Pig{ int a =50} 那么這個a 肯定是在堆里的,有什么問題可以交流。  回復  更多評論
      

    # re: java內存分配研究 2008-06-17 14:39 dennis
    @Jack.Wang
    再回你下吧,class的實例變量并不都是在堆中的,要看是什么類型,如果是int a=5這樣的基本類型字面量,都是直接存儲在常量池的,常量池在方法區,而非堆。就此打住,你的blog,我也不會來了,也麻煩您別去我的blog上亂發消息。  回復  更多評論
      

    # re: java內存分配研究 2008-06-17 16:08 Jack.Wang
    我對常量池不是特別了解,也不能判斷你說的是否準確!

    誰能個詳細的解釋!help me!  回復  更多評論
      

    # re: java內存分配研究 2008-08-27 18:11 Jack.Wang
    @dennis
    親愛的 dennis,好久不去你的 blog 了,今日又看到了我的這篇文檔,你的回復,確實不夠嚴謹,int a=5 是放在常量池里,這是每個類都必須這么做的,當我們實例化一個對象的時候,是用它來初始化對象的,每個對象都可以改變自己的 a 值,因此對于每個對象來說 a 在堆中存儲的,  回復  更多評論
      

    # re: java內存分配研究[未登錄] 2013-10-12 10:31 wfeng007
    @Jack.Wang
    你誤導了太多的人。。
    。。。。。
    現在對于常量池的解釋一片混亂。
    說 在堆區 在棧區 在靜態區 在數據區 的都有。。
    媽的。。。
      回復  更多評論
      

    主站蜘蛛池模板: 亚洲国产精品免费观看| 中文字幕亚洲综合久久| 另类小说亚洲色图| 麻豆国产VA免费精品高清在线 | 亚洲欧美日韩中文二区| 精品无码人妻一区二区免费蜜桃| 亚洲国产精品VA在线观看麻豆| 本免费AV无码专区一区| 亚洲中文字幕无码永久在线 | 在线观看永久免费| 亚洲日韩乱码久久久久久| 18禁成人网站免费观看| 亚洲区视频在线观看| 无码专区永久免费AV网站| 亚洲中文字幕无码av| 国产免费131美女视频| 黄色毛片免费观看| 亚洲精品无码MV在线观看| 亚洲国产精品免费视频| 亚洲人成电影青青在线播放| 色吊丝永久在线观看最新免费| 在线91精品亚洲网站精品成人| 国产成人亚洲精品91专区手机| 久草福利资源网站免费| 亚洲国产精品一区二区久| 日韩免费a级在线观看| 久久国产精品免费一区| 亚洲欧洲第一a在线观看| 日韩精品成人无码专区免费 | 久久国产亚洲观看| 中国在线观看免费国语版| 亚洲国产精品久久久久秋霞小 | 亚洲欧洲国产成人精品| 四虎影视www四虎免费| 日本特黄特色AAA大片免费| 亚洲AV无码一区二区三区系列| 成年私人影院免费视频网站| a在线视频免费观看在线视频三区| 亚洲五月激情综合图片区| 成人免费无毒在线观看网站| 黄视频在线观看免费|