国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

詳解Java的堆內(nèi)存與棧內(nèi)存的存儲(chǔ)機(jī)制

2019-11-26 14:42:23
字體:
供稿:網(wǎng)友

堆與內(nèi)存優(yōu)化
    今天測(cè)了一個(gè)項(xiàng)目的數(shù)據(jù)自動(dòng)整理功能,對(duì)數(shù)據(jù)庫中幾萬條記錄及圖片進(jìn)行整理操作,運(yùn)行接近到最后,爆出了java.lang.outOfMemoryError,java heap space方面的錯(cuò)誤,以前寫程序很少遇到這種內(nèi)存上的錯(cuò)誤,因?yàn)閖ava有垃圾回收器機(jī)制,就一直沒太關(guān)注。今天上網(wǎng)找了點(diǎn)資料,在此基礎(chǔ)上做了個(gè)整理。

 一、堆和棧

    堆―用new建立,垃圾回收器負(fù)責(zé)回收

         1、程序開始運(yùn)行時(shí),JVM從OS獲取一些內(nèi)存,部分是堆內(nèi)存。堆內(nèi)存通常在存儲(chǔ)地址的底層,向上排列。              

         2、堆是一個(gè)"運(yùn)行時(shí)"數(shù)據(jù)區(qū),類實(shí)例化的對(duì)象就是從堆上去分配空間的;       

         3、在堆上分配空間是通過"new"等指令建立的,堆是動(dòng)態(tài)分配的內(nèi)存大小,生存期也不必事先告訴編譯器;

         4、與C++不同的是,Java自動(dòng)管理堆和棧,垃圾回收器可以自動(dòng)回收不再使用的堆內(nèi)存;       

         5、缺點(diǎn)是,由于要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,所以內(nèi)存的存取速度較慢。

    棧―存放基本類型和引用類型,速度快

         1、先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),通常用于保存方法中的參數(shù),局部變量;

         2、在java中,所有基本類型(short,int, long, byte, float, double,boolean, char)和引用類型的變量都在棧中存儲(chǔ);

         3、棧中數(shù)據(jù)的生存空間一般在當(dāng)前scopes內(nèi)(由{...}括起來的區(qū)域;

         4、棧的存取速度比堆要快,僅次于直接位于CPU中的寄存器;

         5、棧中的數(shù)據(jù)可以共享,多個(gè)引用可以指向同一個(gè)地址;

         6、缺點(diǎn)是,棧的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。

 二、內(nèi)存設(shè)置

        1、查看虛擬機(jī)內(nèi)存情況  

long maxControl = Runtime.getRuntime().maxMemory();//獲取虛擬機(jī)可以控制的最大內(nèi)存數(shù)量 long currentUse = Runtime.getRuntime().totalMemory();//獲取虛擬機(jī)當(dāng)前已使用的內(nèi)存數(shù)量 

                默認(rèn)情況下,java虛擬機(jī)的maxControl=66650112B=63.5625M;

                什么都不做的情況,在我的機(jī)子上測(cè)得的currentUse=5177344B=4.9375M;

          2、設(shè)置內(nèi)存大小的命令

             -Xms<size> set initial Java heap size :設(shè)置JVM初始化堆內(nèi)存大小;此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。

             -Xmx<size> set maximum Java heap size:設(shè)置JVM最大的堆內(nèi)存大小;

            -Xmn<size>:設(shè)置年輕代大小,整個(gè)堆大小=年輕代大小+ 年老代大小+ 持久代大小。

             -Xss<size> set java thread stack size:設(shè)置JVM線程棧內(nèi)存大小;
          3、具體操作
             (1)JVM內(nèi)存設(shè)置:
              打開MyEclipse(Eclipse)  window-preferences-Java -Installed JREs -Edit -Default VM Arguments  
              在VM自變量中輸入:-Xmx128m -Xms64m -Xmn32m -Xss16m

             (2)IDE內(nèi)存設(shè)置:

               在MyEclipse根目錄下的myeclipse.ini(或Eclipse根目錄下的eclipse.ini)中修改-vmargs  下的配置:

              (3)Tomcat內(nèi)存設(shè)置

                   打開Tomcat根目錄下的bin文件夾,編輯catalina.bat

                  修改為:set JAVA_OPTS= -Xms256m -Xmx512m

 三、Java堆中的OutOfMemoryError錯(cuò)誤分析

       當(dāng)JVM啟動(dòng)時(shí),使用了-Xms 參數(shù)設(shè)置的堆內(nèi)存。當(dāng)程序繼續(xù)進(jìn)行,創(chuàng)建更多對(duì)象,JVM開始擴(kuò)大堆內(nèi)存以容納更多對(duì)象。JVM也會(huì)使用垃圾回收器來回收內(nèi)存。當(dāng)快達(dá)到-Xmx設(shè)置的最大堆內(nèi)存時(shí),如果沒有更多的內(nèi)存可被分配給新對(duì)象的話,JVM就會(huì)拋出java.lang.outofmemoryerror,程序就會(huì)宕掉。在拋出 OutOfMemoryError之前,JVM會(huì)嘗試著用垃圾回收器來釋放足夠的空間,但是發(fā)現(xiàn)仍舊沒有足夠的空間時(shí),就會(huì)拋出這個(gè)錯(cuò)誤。為了解決這個(gè)問題,需要清楚程序?qū)ο蟮男畔ⅲ纾銊?chuàng)建了哪些對(duì)象,哪些對(duì)象占用了多少空間等等。可以使用profiler或者堆分析器來處理OutOfMemoryError錯(cuò)誤。"java.lang.OutOfMemoryError: Java heap space”表示堆沒有足夠的空間了,不能繼續(xù)擴(kuò)大了。"java.lang.OutOfMemoryError: PermGen space”表示permanent generation已經(jīng)裝滿了,你的程序不能再裝載類或者再分配一個(gè)字符串了。

四、堆和垃圾回收

  我們知道對(duì)象創(chuàng)建在堆內(nèi)存中,垃圾回收這樣一個(gè)進(jìn)程,它將已死對(duì)象清除出堆空間,并將這些內(nèi)存再還給堆。為了給垃圾回收器使用,堆主要分成三個(gè)區(qū)域,分別叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation是用來存放新建的對(duì)象的空間,在對(duì)象新建的時(shí)候被使用。如果長(zhǎng)時(shí)間還使用的話,它們會(huì)被垃圾回收器移動(dòng)到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta數(shù)據(jù)的地方,例如類,方法,字符串池和類級(jí)別的詳細(xì)信息。

 五、總結(jié):

  1、Java堆內(nèi)存是操作系統(tǒng)分配給JVM的內(nèi)存的一部分。

  2、當(dāng)我們創(chuàng)建對(duì)象時(shí),它們存儲(chǔ)在Java堆內(nèi)存中。

  3、為了便于垃圾回收,Java堆空間分成三個(gè)區(qū)域,分別叫作New Generation, Old Generation或叫作Tenured Generation,還有Perm Space。

  4、你可以通過用JVM的命令行選項(xiàng) -Xms, -Xmx, -Xmn來調(diào)整Java堆空間的大小。

  5、可以用JConsole或者Runtime.maxMemory(),Runtime.totalMemory(),Runtime.freeMemory()來查看Java中堆內(nèi)存的大小。

  6、可以使用命令“jmap”來獲得heap dump,用“jhat”來分析heap dump。

  7、Java堆空間不同于棧空間,棧空間是用來儲(chǔ)存調(diào)用棧和局部變量的。

  8、Java垃圾回收器是用來將死掉的對(duì)象(不再使用的對(duì)象)所占用的內(nèi)存回收回來,再釋放到Java堆空間中。

  9、當(dāng)遇到j(luò)ava.lang.outOfMemoryError時(shí),不必緊張,有時(shí)候僅僅增加堆空間就可以了,但如果經(jīng)常出現(xiàn)的話,就要看看Java程序中是不是存在內(nèi)存泄露了。

  10、使用Profiler和Heap dump分析工具來查看Java堆空間,可以查看給每個(gè)對(duì)象分配了多少內(nèi)存。

棧存儲(chǔ)詳解

Java棧存儲(chǔ)具有以下幾個(gè)特點(diǎn):

一、存在棧中的數(shù)據(jù)大小和生命周期必須是確定的。

      如基本類型的存儲(chǔ):int a = 1; 這種變量存的是字面值,a是一個(gè)指向int類型的引用,指向3這個(gè)字面值。這些字面值的數(shù)據(jù),由于大小可知,生存期可知(這些字面值固定定義在某個(gè)程序塊里面,程序塊退出后,字面值就消失了),出于追求速度的原因,就存在于棧中。

二、存在棧中的數(shù)據(jù)可以共享。

    (1)、基本類型數(shù)據(jù)存儲(chǔ):

   如:

int a = 3;       int b = 3; 

 編譯器先處理int a = 3;首先它會(huì)在棧中創(chuàng)建一個(gè)變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個(gè)存放3這個(gè)字面值的地址,然后將a指向3的地址。接著處理int b = 3;在創(chuàng)建完b的引用變量后,由于在棧中已經(jīng)有3這個(gè)字面值,便將b直接指向3的地址。這樣,就出現(xiàn)了a與b同時(shí)均指向3的情況。

注意:這種字面值的引用與類對(duì)象的引用不同。假定兩個(gè)類對(duì)象的引用同時(shí)指向一個(gè)對(duì)象,如果一個(gè)對(duì)象引用變量修改了這個(gè)對(duì)象的內(nèi)部狀態(tài),那么另一個(gè)對(duì)象引用變量也即刻反映出這個(gè)變化。相反,通過字面值的引用來修改其值,不會(huì)導(dǎo)致另一個(gè)指向此字面值的引用的值也跟著改變的情況。如上例,我們定義完a 與b的值后,再令a=4;那么,b不會(huì)等于4,還是等于3。在編譯器內(nèi)部,遇到a=4;時(shí),它就會(huì)重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經(jīng)有了,則直接將a指向這個(gè)地址。因此a值的改變不會(huì)影響到b的值。

   (2)、包裝類數(shù)據(jù)存儲(chǔ):

   如Integer, Double, String等將相應(yīng)的基本數(shù)據(jù)類型包裝起來的類。這些類數(shù)據(jù)全部存在于堆中,Java用new()語句來顯示地告訴編譯器,在運(yùn)行時(shí)才根據(jù)需要?jiǎng)討B(tài)創(chuàng)建,因此比較靈活,但缺點(diǎn)是要占用更多的時(shí)間。

   如:以String為例。

    String是一個(gè)特殊的包裝類數(shù)據(jù)。即可以用String str = new String("abc");的形式來創(chuàng)建,也可以用String str = "abc";的形式來創(chuàng)建。前者是規(guī)范的類的創(chuàng)建過程,即在Java中,一切都是對(duì)象,而對(duì)象是類的實(shí)例,全部通過new()的形式來創(chuàng)建。Java 中的有些類,如DateFormat類,可以通過該類的getInstance()方法來返回一個(gè)新創(chuàng)建的類,似乎違反了此原則。其實(shí)不然。該類運(yùn)用了單例模式來返回類的實(shí)例,只不過這個(gè)實(shí)例是在該類內(nèi)部通過new()來創(chuàng)建的,而getInstance()向外部隱藏了此細(xì)節(jié)。

    那為什么在String str = "abc";中,并沒有通過new()來創(chuàng)建實(shí)例,是不是違反了上述原則?其實(shí)沒有。

    關(guān)于String str = "abc"的內(nèi)部工作。Java內(nèi)部將此語句轉(zhuǎn)化為以下幾個(gè)步驟:
  a、先定義一個(gè)名為str的對(duì)String類的對(duì)象引用變量:String str;
  b、在棧中查找有沒有存放值為"abc"的地址,如果沒有,則開辟一個(gè)存放字面值為"abc"的地址,接著創(chuàng)建一個(gè)新的String類的對(duì)象O,并將O的字符串值指向這個(gè)地址,而且在棧中這個(gè)地址旁邊記下這個(gè)引用的對(duì)象O。如果已經(jīng)有了值為"abc"的地址,則查找對(duì)象O,并返回O的地址。
    c、將str指向?qū)ο驩的地址。
 值得注意的是,通常String類中字符串值都是直接存值的。但像String str = "abc";這種場(chǎng)合下,其字符串值卻是保存了一個(gè)指向存在棧中數(shù)據(jù)的引用(即:String str = "abc";既有棧存儲(chǔ),又有堆存儲(chǔ))。

  為了更好地說明這個(gè)問題,我們可以通過以下的幾個(gè)代碼進(jìn)行驗(yàn)證。

   String str1 = "abc";   String str2 = "abc";   System.out.println(str1==str2); //true 

(只有在兩個(gè)引用都指向了同一個(gè)對(duì)象時(shí)才返回真值。str1與str2是否都指向了同一個(gè)對(duì)象)

  結(jié)果說明,JVM創(chuàng)建了兩個(gè)引用str1和str2,但只創(chuàng)建了一個(gè)對(duì)象,而且兩個(gè)引用都指向了這個(gè)對(duì)象。

   String str1 = "abc";   String str2 = "abc";   str1 = "bcd";   System.out.println(str1 + "," + str2); //bcd, abc   System.out.println(str1==str2); //false 

   這就是說,賦值的變化導(dǎo)致了類對(duì)象引用的變化,str1指向了另外一個(gè)新對(duì)象,而str2仍舊指向原來的對(duì)象。上例中,當(dāng)我們將str1的值改為"bcd"時(shí),JVM發(fā)現(xiàn)在棧中沒有存放該值的地址,便開辟了這個(gè)地址,并創(chuàng)建了一個(gè)新的對(duì)象,其字符串的值指向這個(gè)地址。

  事實(shí)上,String類被設(shè)計(jì)成為不可改變(immutable)的類。如果你要改變其值,可以,但JVM在運(yùn)行時(shí)根據(jù)新值悄悄創(chuàng)建了一個(gè)新對(duì)象(沒法在原來內(nèi)存的基礎(chǔ)上改變其值),然后將這個(gè)對(duì)象的地址返回給原來類的引用。這個(gè)創(chuàng)建過程雖說是完全自動(dòng)進(jìn)行的,但它畢竟占用了更多的時(shí)間。在對(duì)時(shí)間要求比較敏感的環(huán)境中,會(huì)帶有一定的不良影響。

   String str1 = "abc";   String str2 = "abc";   str1 = "bcd";   String str3 = str1;   System.out.println(str3); //bcd   String str4 = "bcd";   System.out.println(str1 == str4); //true 

   str3這個(gè)對(duì)象的引用直接指向str1所指向的對(duì)象(注意,str3并沒有創(chuàng)建新對(duì)象)。當(dāng)str1改完其值后,再創(chuàng)建一個(gè)String的引用str4,并指向因str1修改值而創(chuàng)建的新的對(duì)象。可以發(fā)現(xiàn),這回str4也沒有創(chuàng)建新的對(duì)象,從而再次實(shí)現(xiàn)棧中數(shù)據(jù)的共享。


   String str1 = new String("abc");   String str2 = "abc";   System.out.println(str1==str2); //false 

 創(chuàng)建了兩個(gè)引用。創(chuàng)建了兩個(gè)對(duì)象。兩個(gè)引用分別指向不同的兩個(gè)對(duì)象。

   

  String str1 = "abc";   String str2 = new String("abc");   System.out.println(str1==str2); //false 

 創(chuàng)建了兩個(gè)引用。創(chuàng)建了兩個(gè)對(duì)象。兩個(gè)引用分別指向不同的兩個(gè)對(duì)象。

  以上兩段代碼說明,只要是用new()來新建對(duì)象的,都會(huì)在堆中創(chuàng)建,而且其字符串是單獨(dú)存值的,即使與棧中的數(shù)據(jù)相同,也不會(huì)與棧中的數(shù)據(jù)共享。

 總結(jié):

  (1)我們?cè)谑褂弥T如String str = "abc";的格式定義類時(shí),總是想當(dāng)然地認(rèn)為,我們創(chuàng)建了String類的對(duì)象str。擔(dān)心陷阱!對(duì)象可能并沒有被創(chuàng)建!唯一可以肯定的是,指向 String類的引用被創(chuàng)建了。至于這個(gè)引用到底是否指向了一個(gè)新的對(duì)象,必須根據(jù)上下文來考慮,除非你通過new()方法來顯要地創(chuàng)建一個(gè)新的對(duì)象。因此,更為準(zhǔn)確的說法是,我們創(chuàng)建了一個(gè)指向String類的對(duì)象的引用變量str,這個(gè)對(duì)象引用變量指向了某個(gè)值為"abc"的String類。清醒地認(rèn)識(shí)到這一點(diǎn)對(duì)排除程序中難以發(fā)現(xiàn)的bug是很有幫助的。

  (2)使用String str = "abc";的方式,可以在一定程度上提高程序的運(yùn)行速度,因?yàn)镴VM會(huì)自動(dòng)根據(jù)棧中數(shù)據(jù)的實(shí)際情況來決定是否有必要?jiǎng)?chuàng)建新對(duì)象。而對(duì)于String str = new String("abc");的代碼,則一概在堆中創(chuàng)建新對(duì)象,而不管其字符串值是否相等,是否有必要?jiǎng)?chuàng)建新對(duì)象,從而加重了程序的負(fù)擔(dān)。


  (3)由于String類的immutable性質(zhì)(因?yàn)榘b類的值不可修改),當(dāng)String變量需要經(jīng)常變換其值時(shí),應(yīng)該考慮使用StringBuffer類,以提高程序效率。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 新丰县| 手游| 滦平县| 乳源| 久治县| 潜江市| 湖口县| 伊宁市| 徐闻县| 迁安市| 竹溪县| 湾仔区| 东港市| 兰溪市| 白朗县| 霍州市| 舒兰市| 蛟河市| 长白| 安阳县| 清丰县| 饶阳县| 房产| 沙洋县| 灯塔市| 新蔡县| 兴海县| 远安县| 蒙山县| 正定县| 达州市| 邢台县| 方山县| 紫阳县| 巢湖市| 太谷县| 津市市| 海原县| 迁安市| 利津县| 青龙|