1.java是如何管理內存的java
java的內存管理就是對象的分配和釋放問題。(其中包括兩部分)程序員
分 配:內存的分配是由程序完成的,程序員須要經過關鍵字new爲每一個對象申請內存空間(基本類型除外),全部的對象都在堆(Heap)中分配空間。 釋放:對象的釋放是由垃圾回收機制決定和執行的,這樣作確實簡化了程序員的工做。但同時,它也加劇了JVM的工做。由於,GC爲了可以正確釋放對象,GC 必須監控每個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都須要進行監控。算法
2.什麼叫java的內存泄露數據庫
在java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色,首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連(也就是說仍存在該內存對象的引用);其次,這些對象是無用的,即程序之後不會再使用這些對象。若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。數組
3.JVM的內存區域組成緩存
java把內存分兩種:多線程
一種是棧內存,另外一種是堆內存app
(1)在函數中定義的基本類型變量(即基本類型的局部變量)和對象的引用變量(即對象的變量名)都在函數的棧內存中分配;jvm
(2)堆內存用來存放由new建立的對象和數組以及對象的實例變量(即全局變量)。函數
在函數(代碼塊)中定義一個變量時,java就在棧中爲這個變量分配內存空間,當超過變量的做用域後,java會自動釋放掉爲該變量所分配的內存空間;
在堆中分配的內存由java虛擬機的自動垃圾回收器來管理
堆和棧的優缺點
堆的優點是能夠動態分配內存大小,生存期也沒必要事先告訴編譯器,由於它是在運行時動態分配內存的。
缺點就是要在運行時動態分配內存,存取速度較慢;
棧的優點是,存取速度比堆要快,僅次於直接位於CPU中的寄存器。
另外,棧數據能夠共享。
但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。
此外補充一下java中還有一個方法區的東東:
方法區中主要存儲全部對象數據共享區域,存儲靜態變量和普通方法、靜態方法、常量、字符串常量(嚴格說存放在常量池,堆和棧都有)等類信息,、說白了就是保存類的模板
如下內容也不知道在哪裏copy到的,你們能夠隨便看看
堆區:
1.存儲的所有是對象,每一個對象都包含一個與之對應的class的信息。(class的目的是獲得操做指令)
2.jvm只有一個堆區(heap)被全部線程共享,堆中不存放基本類型和對象引用,只存放對象自己
棧區:
1.每一個線程包含一個棧區,棧中只保存基礎數據類型的對象和自定義對象的引用(不是對象),對象都存放在堆區中
2.每一個棧中的數據(原始類型和對象引用)都是私有的,其餘棧不能訪問。
3.棧分爲3個部分:基本類型變量區、執行環境上下文、操做指令區(存放操做指令)。
方法區:
1.又叫靜態區,跟堆同樣,被全部的線程共享。方法區包含全部的class和static變量。
2.方法區中包含的都是在整個程序中永遠惟一的元素,如class,static變量。
對於java中存儲區域的講解能夠參考下面的的地址:
http://bbs.csdn.net/topics/370001490
4.java中數據在內存中是如何存儲的
a)基本數據類型
java 的基本數據類型共有8種,即int,short,long,byte,float,double,boolean,char(注意,並無String的 基本類型 )。這種類型的定義是經過諸如int a = 3;long b = 255L;的形式來定義的。如int a = 3;這裏的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,因爲大小可知,生存期可知(這些字面值定義在某個程序塊裏面,程序塊退出 後,字段值就消失了),出於追求速度的緣由,就存在於棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據能夠共享。好比: 咱們同時定義:
int a=3; int b=3;
編 譯器先處理int a = 3;首先它會在棧中建立一個變量爲a的引用,而後查找有沒有字面值爲3的地址,沒找到,就開闢一個存放3這個字面值的地址,而後將a指向3的地址。接着處 理int b = 3;在建立完b這個引用變量後,因爲在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的狀況。
定義完a與b的值後,再令a = 4;那麼,b不會等於4,仍是等於3。在編譯器內部,遇到時,它就會從新搜索棧中是否有4的字面值,若是沒有,從新開闢地址存放4的值;若是已經有了,則直接將a指向這個地址。所以a值的改變不會影響到b的值。
b)對象
在java中,建立一個對象包括對象的聲明和實例化兩步,下面用一個例題來講明對象的內存模型。假設有類Rectangle定義以下:
(1) 聲明對象時的內存模型 用Rectangle rect;聲明一個對象rect時,將在棧內存爲對象的引用變量rect分配內存空間,但Rectangle的值爲空,稱rect是一個空對象。空對象不 能使用,由於它尚未引用任何」實體」。 (2)對象實例化時的內存模型 當執行rect=new Rectangle(3,5);時,會作兩件事:在堆內存中爲類的成員變量width,height分配內存,並將其初始化爲各數據類型的默認值;接着進 行顯式初始化(類定義時的初始化值);最後調用構造方法,爲成員變量賦值。返回堆內存中對象的引用(至關於首地址)給引用變量rect,之後就能夠經過 rect來引用堆內存中的對象了。
c)建立多個不一樣的對象實例
一個類經過使用new運算符能夠建立多個不一樣的對象實例,這些對象實例將在堆中被分配不一樣的內存空間,改變其中一個對象的狀態不會影響其餘對象的狀態。例如:
此時,將在堆內存中分別爲兩個對象的成員變量 width 、 height 分配內存空間,兩個對象在堆內存中佔據的空間是互不相同的。若是有:
則在堆內存中只建立了一個對象實例,在棧內存中建立了兩個對象引用,兩個對象引用同時指向一個對象實例。
d)包裝類
基 本類型都有對應的包裝類:如int對應Integer類,double對應Double類等,基本類型的定義都是直接在棧中,若是用包裝類來建立對象,就 和普通對象同樣了。例如:int i=0;i直接存儲在棧中。Integer i(i此時是對象)= new Integer(5);這樣,i對象數據存儲在堆中,i的引用存儲在棧中,經過棧中的引用來操做對象。
e)String
String 是一個特殊的包裝類數據。能夠用如下兩種方式建立:String str = new String(「abc」);String str = 「abc」; 第一種建立方式,和普通對象的的建立過程同樣; 第二種建立方式,java內部將此語句轉化爲如下幾個步驟: (1)先定義一個名爲str的對String類的對象引用變量:String str; (2)在棧中查找有沒有存放值爲」abc」的地址,若是沒有,則開闢一個存放字面值爲」abc」 地址,接着建立一個新的String類的對象o,並將o的字符串值指向這個地址,並且在棧 這個地址旁邊記下這個引用的對象o。若是已經有了值爲」abc」的地址,則查找對象o,並 回o的地址。 (3)將str指向對象o的地址。 值得注意的是,通常String類中字符串值都是直接存值的。但像String str = 「abc」;這種 合下,其字符串值倒是保存了一個指向存在棧中數據的引用。 爲了更好地說明這個問題,咱們能夠經過如下的幾個代碼進行驗證。
注 意,這裏並不用 str1.equals(str2);的方式,由於這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了 同一個對象時才返回真值。而咱們在這裏要看的是,str1與str2是否都指向了同一個對象。 咱們再接着看如下的代碼。
建立了兩個引用。建立了兩個對象。兩個引用分別指向不一樣的兩個對象。 以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中建立,並且其字符串是單獨存值的,即便與棧中的數據相同,也不會與棧中的數據共享。
f)數組
當定義一個數組,int x[];或int[] x;時,在棧內存中建立一個數組引用,經過該引用(即數組名)來引用數組。x=new int[3];將在堆內存中分配3個保存 int型數據的空間,堆內存的首地址放到棧內存中,每一個數組元素被初始化爲0。
g)靜態變量
用 static的修飾的變量和方法,其實是指定了這些變量和方法在內存中的」固定位置」-static storage,能夠理解爲全部實例對象共有的內存空間。static變量有點相似於C中的全局變量的概念;靜態表示的是內存的共享,就是它的每個實例 都指向同一個內存地址。把static拿來,就是告訴JVM它是靜態的,它的引用(含間接引用)都是指向同一個位置,在那個地方,你把它改了,它就不會變 成原樣,你把它清理了,它就不會回來了。
那 靜態變量與方法是在何時初始化的呢?對於兩種不一樣的類屬性,static屬性與instance屬性,初始化的時機是不一樣的。instance屬性在 建立實例的時候初始化,static屬性在類加載,也就是第一次用到這個類的時候初始化,對於後來的實例的建立,再也不次進行初始化。
咱們常可看到相似如下的例子來講明這個問題:
每一次建立一個新的Student實例時,成員numberOfStudents都會不斷的遞增,而且全部的Student實例都訪問同一個numberOfStudents變量,實際上intnumberOfStudents變量在內存中只存儲在一個位置上。
5.java的內存管理實例
Java程序的多個部分(方法,變量,對象)駐留在內存中如下兩個位置:即堆和棧,如今咱們只關心三類事物:實例變量,局部變量和對象: 實例變量和對象駐留在堆上 局部變量駐留在棧上 讓咱們查看一個 java 程序,看看他的各部分如何建立而且映射到棧和堆中:
6. 垃圾回收機制
問題一:什麼叫垃圾回收機制? 垃圾回收是一種動態存儲管理技術,它自動地釋放再也不被程序引用的對象,按照特定的垃圾收集算法來實現資源自動回收的功能。當一個對象再也不被引用的時候,內存回收它佔領的空間,以便空間被後來的新對象使用,以避免形成內存泄露。
問 題二:java的垃圾回收有什麼特色? jAVA語言不容許程序員直接控制內存空間的使用。內存空間的分配和回收都是由JRE負責在後臺自動進行的,尤爲是無用內存空間的回收操做 (garbagecollection,也稱垃圾回收),只能由運行環境提供的一個超級線程進行監測和控制。
問題三:垃圾回收器何時會運行? 通常是在CPU空閒或空間不足時自動進行垃圾回收,而程序員沒法精確控制垃圾回收的時機和順序等。、
問題四:什麼樣的對象符合垃圾回收條件? 當沒有任何得到線程能訪問一個對象時,該對象就符合垃圾回收條件。
問 題五:垃圾回收器是怎樣工做的? 垃圾回收器如發現一個對象不能被任何活線程訪問時,他將認爲該對象符合刪除條件,就將其加入回收隊列,但不是當即銷燬對象,什麼時候銷燬並釋放內存是沒法預知 的。垃圾回收不能強制執行,然而java提供了一些方法(如:System.gc()方法),容許你請求JVM執行垃圾回收,而不是要求,虛擬機會盡其所 能知足請求,可是不能保證JVM從內存中刪除全部不用的對象。
問題六:一個java程序可以耗盡內存嗎? 能夠。垃圾收集系統嘗試在對象不被使用時把他們從內存中刪除。然而,若是保持太多活的對象,系統則可能會耗盡內存。垃圾回收器不能保證有足夠的內存,只能保證可用內存儘量的獲得高效的管理。
問題七:如何顯示的使對象符合垃圾回收條件? (1)空引用:當對象沒有對他可到達引用時,他就符合垃圾回收的條件。也就是說若是沒有對他的引用,刪除對象的引用就能夠達到目的,所以咱們能夠把引用變量設置爲null,來符合垃圾回收的條件。
(2) 從新爲引用變量賦值:能夠經過設置引用變量引用另外一個對象來解除該引用變量與一個對象間的引用關係。 StringBuffer sb1 = new StringBuffer(「hello」); StringBuffer sb2 = new StringBuffer(「goodbye」); System.out.println(sb1); sb1=sb2;//此時」hello」符合回收條件 (3)方法內建立的對象:所建立的局部變量僅在該方法的做用期間內存在。一旦該方法返回,在這個方法內建立的對象就符合垃圾收集條件。有一種明顯的例外情 況,就是方法的返回對象。
(4)隔離引用:這種狀況中,被回收的對象仍具備引用,這種狀況稱做隔離島。若存在這兩個實例,他們互相引用,而且這兩個對象的全部其餘引用都刪除,其餘任何線程沒法訪問這兩個對象中的任意一個。也能夠符合垃圾回收條件。
問 題八:垃圾收集前進行清理——finalize()方法 java提供了一種機制,使你可以在對象剛要被垃圾回收以前運行一些代碼。這段代碼位於名爲finalize()的方法內,全部類從Object類繼承這 個方法。因爲不能保證垃圾回收器會刪除某個對象。所以放在finalize()中的代碼沒法保證運行。所以建議不要重寫finalize();
7.final問題
final使得被修飾的變量」不變」,可是因爲對象型變量的本質是」引用」,使得」不變」也有了兩種含義:引用自己的不變和引用指向的對象不變。 引用自己的不變:
a=b;//編譯期錯誤
引用指向的對象不變:
可 見,final只對引用的」值」(也即它所指向的那個對象的內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會致使編譯期錯誤。至於 它所指向的對象的變化,final是不負責的。這很相似==操做符:==操做符只負責引用的」值」相等,至於這個地址所指向的對象內容是否相等,==操做 符是無論的。在舉一個例子:
編寫測試方法:
理 解final問題有很重要的含義。許多程序漏洞都基於此—-final只能保證引用永遠指向固定對象,不能保證那個對象的狀態不變。在多線程的操做中,一 個對象會被多個線程共享或修改,一個線程對對象無心識的修改可能會致使另外一個使用此對象的線程崩潰。一個錯誤的解決方法就是在此對象新建的時候把它聲明爲 final,意圖使得它」永遠不變」。其實那是徒勞的.final還有一個值得注意的地方, 先看如下示例程序:
對 於類變量,java虛擬機會自動進行初始化。若是給出了初始值,則初始化爲該初始值。若是沒有給出,則把它初始化爲該類型變量的默認初始值。可是對於用 final修飾的類變量,虛擬機不會爲其賦予初值,必須在constructor(構造器)結束以前被賦予一個明確的值。能夠修改成」final int i = 0;」。
8.如何把程序寫得更健壯
(1) 儘早釋放無用對象的引用。 好的辦法是使用臨時變量的時候,讓引用變量在退出活動域後,自動設置爲null,暗示垃圾收集器來收集該對象,防止發生內存泄露。對於仍然有指針指向的實 例,jvm就不會回收該資源,由於垃圾回收會將值爲null的對象做爲垃圾,提升GC回收機制效率;
(2) 定義字符串應該儘可能使用String str=」hello」;的形式,避免使用String str = new String(「hello」);的形式。由於要使用內容相同的字符串,沒必要每次都new一個String。例如咱們要在構造器中對一個名叫s的 String引用變量進行初始化,把它設置爲初始值,應當這樣作:
後者每次都會調用構造器,生成新對象,性能低下且內存開銷大,而且沒有意義,由於String對象不可改變,因此對於內容相同的字符串,只要一個String對象來表示就能夠了。也就說,屢次調用上面的構造器建立多個對象,他們的String類型屬性s都指向同一個對象。
(3)咱們的程序裏不可避免大量使用字符串處理,避免使用String,應大量使用StringBuffer,由於String被設計成不可變(immutable)類,因此它的全部對象都是不可變對象,請看下列代碼;
在 這段代碼中,s原先指向一個String對象,內容是」Hello」,而後咱們對s進行了+操做,那麼s所指向的那個對象是否發生了改變呢?答案是沒有。 這時,s不指向原來那個對象了,而指向了另外一個String對象,內容爲」Hello world!」,原來那個對象還存在於內存之中,只是s這個引用變量再也不指向它了。
通 過上面的說明,咱們很容易導出另外一個結論,若是常常對字符串進行各類各樣的修改,或者說,不可預見的修改,那麼使用String來表明字符串的話會引發很 大的內存開銷。由於String對象創建以後不能再改變,因此對於每個不一樣的字符串,都須要一個String對象來表示。這時,應該考慮使用 StringBuffer類,它容許修改,而不是每一個不一樣的字符串都要生成一個新的對象。而且,這兩種類的對象轉換十分容易。
(4)儘可能少用靜態變量,由於靜態變量是全局的,GC不會回收的;
(5) 儘可能避免在類的構造函數裏建立、初始化大量的對象,防止在調用其自身類的構造器時形成沒必要要的內存資源浪費,尤爲是大對象,JVM會忽然須要大量內存,這 時必然會觸發GC優化系統內存環境;顯示的聲明數組空間,並且申請數量還極大。 如下是初始化不一樣類型的對象須要消耗的時間:
運算操做 |
示例 |
標準化時間 |
本地賦值 |
i = n |
1.0 |
實例賦值 |
this.i = n |
1.2 |
方法調用 |
Funct() |
5.9 |
新建對象 |
New Object() |
980 |
新建數組 |
New int[10] |
3100 |
從表中能夠看出,新建一個對象須要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而新建一個數組所花費的時間就更多了。
(6)儘可能在合適的場景下使用對象池技術以提升系統性能,縮減縮減開銷,可是要注意對象池的尺寸不宜過大,及時清除無效對象釋放內存資源,綜合考慮應用運行環境的內存資源限制,避免太高估計運行環境所提供內存資源的數量。
(7)大集合對象擁有大數據量的業務對象的時候,能夠考慮分塊進行處理,而後解決一塊釋放一塊的策略。
(8)不要在常常調用的方法中建立對象,尤爲是忌諱在循環中建立對象。能夠適當的使用hashtable,vector建立一組對象容器,而後從容器中去取那些對象,而不用每次new以後又丟棄。
(9)通常都是發生在開啓大型文件或跟數據庫一次拿了太多的數據,形成Out Of Memory Error的情況,這時就大概要計算一下數據量的最大值是多少,而且設定所需最小及最大的內存空間值。
(10)儘可能少用finalize函數,由於finalize()會加大GC的工做量,而GC至關於耗費系統的計算能力。
(11) 不要過濫使用哈希表,有必定開發經驗的開發人員常常會使用hash表(hash表在JDK中的一個實現就是HashMap)來緩存一些數據,從而提升系統 的運行速度。好比使用HashMap緩存一些物料信息、人員信息等基礎資料,這在提升系統速度的同時也加大了系統的內存佔用,特別是當緩存的資料比較多的 時候。其實咱們可使用操做系統中的緩存的概念來解決這個問題,也就是給被緩存的分配一個必定大小的緩存容器,按照必定的算法淘汰不須要繼續緩存的對象, 這樣一方面會由於進行了對象緩存而提升了系統的運行效率,同時因爲緩存容器不是無限制擴大,從而也減小了系統的內存佔用。如今有不少開源的緩存實現項目, 好比ehcache、oscache等,這些項目都實現了FIFO 、MRU等常見的緩存算法。