作一個積極的人java
編碼、改bug、提高本身程序員
我有一個樂園,面向編程,春暖花開!編程
推薦閱讀設計模式
第一季
0、Java的線程安全、單例模式、JVM內存結構等知識梳理數組
三、Java內存管理-JVM內存模型以及JDK7和JDK8內存模型對比總結(三)多線程
七、Java內存管理-掌握自定義類加載器的實現(七)
第一季總結:由淺入深JAVA內存管理 Core Story第二季
九、Java內存管理-」一文掌握虛擬機建立對象的祕密」(九)
十、Java內存管理-你真的理解Java中的數據類型嗎(十)
十一、Java內存管理-Stackoverflow問答-Java是傳值仍是傳引用?(十一)
十二、Java內存管理-探索Java中字符串String(十二)
實戰
分享一位老師的人工智能教程。零基礎!通俗易懂!風趣幽默!你們能夠看看是否對本身有幫助,點擊這裏查看【人工智能教程】。接下來進入正文。
勿在流沙築高臺,出來混早晚要還的。
回顧一下:
本文是接着上一篇內容:Java內存管理-愚人節new一個對象送給你(八),繼續整理!主要內容講解HotSpot虛擬機在Java堆中對象是如何建立、內存分配佈局和訪問方式。
本文地圖:
若是你是一直從第一季看過來的,那必定知道前面有個地方講過類的整個生命週期,以前只是講到了初始化階段,類是如何使用和類是如何被卸載尚未進行講解!那本文就簡單介紹一下類的使用,咱們new
一個 「如花」 似玉的girl
!
這裏再回顧一下,類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的生命週期包括了七個階段:
在Java中咱們用使用一個類,不少時候是建立這個類的一個實例,也就是常說的建立一個對象。其實在Java程序運行過程當中,無時無刻都有對象被建立出來。建立對象(如克隆、反序列化)一般僅僅是一個new
關鍵字而已。可是在Java虛擬機中一個對象(只是普通的java對象,不包括數組和Class對象等)的建立是怎麼一個過程呢?
第一:虛擬機遇到一條new
指令時,首先會去檢查這個指令的參數是否可以在常量池中定位到一個類的符號引用。而後檢查這個符號引用表明的類是否已經被加載、解析和初始化過。若是沒有進行類加載則執行相應的類加載的過程。 記住:要new對象,要先加載類!
第二:類加載檢查經過後,虛擬機將爲新生的對象分配內存。對象所需的內存大小在類加載的時候即可以徹底肯定(如何肯定對象的下文說明) 。爲對象分配內存的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。分配方式有 「指針碰撞」 和 「空閒列表」 兩種,選擇那種分配方式由 Java 堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定(對象在堆上的劃分,這是個複雜的問題,後文繼續探討,這裏只要明白是在對象是在堆上分配內存便可)。 記住:要new對象,要有先分配內存空間!
第三:內存分配完成,虛擬機須要將分配的內存空間都初始化爲零值(零值這個概念以前文章也介紹過,這裏就再也不說明),這一步的操做保證了對象的實例字段在Java代碼中能夠不賦初始值就直接使用,由於程序能訪問這些字段的數據類型對應的零值。 記住:要new對象,虛擬機會幫你爲對象的實例字段自動賦予零值!
第四:虛擬機要對對象進行必要的設置,如這個對象是哪一個類的實例、如何才能找到類的元數據信息(JDK7是方法區保存)、對象的哈希碼、對象的GC分代年齡等信息。這些信息都存放在對象的對象頭(Object Header)中。
上面工做都完成以後,在虛擬機看來,一個對象就已經產生了。可是從Java程序的角度看,對象的建立纔剛剛開始,由於
方法還尚未執行,全部的字段都是爲零值。因此通常來講,在new
指令以後會接着執行
方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算徹底產生出來!
記住:對象不是你想new,想new就能夠new的!
下面用經過圖解的例子簡單說明(版本jdk1.7):
第一: 一個PrettyGirl類!
public class PrettyGirl {
/**
* 姑娘姓字名誰
*/
String name;
/**
* 芳齡幾何
*/
int age;
/**
* 家住何方
*/
static String address;
/**
* 可曾婚配否
*/
boolean marry;
void sayHello(){
System.out.println("Hello...");
}
@Override
public String toString() {
return "PrettyGirl{" +
"name='" + name + '\'' +
", age=" + age +
", marry=" + marry +
'}';
}
}
複製代碼
方法區除了保存類的結構,還保存靜態屬性與靜態方法。編寫中小型程序時,通常不會形成方法區的內存溢出!在JDK1.8 沒有方法區的概念,前面文章中也有提到,這裏爲了講解使用圖解仍是JDK1.7!
第二:實例化new兩個漂亮女孩!
public static void main(String[] args) {
PrettyGirl pg1 = new PrettyGirl();
pg1.name = "Alice";
pg1.age = 18;
pg1.address = "changsha";
PrettyGirl pg2 = new PrettyGirl();
pg2.name = "Alexia";
pg2.age = 28;
System.out.println(pg1 + " ---" + pg1.address);
System.out.println(pg2 + "----" + pg2.address);
}
----打印結果:--------
PrettyGirl{name='Alice', age=18, marry=false} ---changsha
PrettyGirl{name='Alexia', age=28, marry=false}----changsha
複製代碼
在棧內存爲 pg1 變量申請一個空間,在堆內存爲PrettyGirl對象申請空間,初始化完畢後將其地址值返回給pg1 ,經過pg1 .name和pg1 .age修改其值,靜態的變量address是類公有的!
堆存放對象持有的數據,同時保持對原類的引用。能夠簡單的理解爲對象屬性的值保存在堆中,對象調用的方法保存在方法區。
從上圖也能夠看到有一個區域是棧,在程序運行的時候,每當遇到方法 調用時候,Java虛擬機就會在棧中劃分一塊內存稱爲棧幀(線程私有,堆和方法區線程共享的)。就如上面的程序,在調用main方法的時候,會建立一下棧,棧幀中的內存供局部變量(包括基本類型和引用類型)使用,基本類型和引用類型後文會詳情介紹。當方法調用結束後,虛擬機會回收次棧幀佔用的內存。
tips: 回顧
一、堆內存溢出會發生 OutOfMemoryError 錯誤,提示信息「Java heap Space」。
二、在棧中會有兩個異常:
三、若是有方法區 也會出現OutOfMemoryError 錯誤,提示信息 「PermGen space」。(JDK8 後無此錯誤提示)
每一個區域都有一些參數能夠設置,參數學習續持續更新!
感慨,建立一個對象仍是挺不容易的!
在HotSpot虛擬機中,對象在內存中的佈局能夠分爲3塊區域:對象頭(Header)、實例數據(Instance data)和對象填充(Padding)。
那下面就對這三塊區域進行簡單介紹:
對象頭包括兩部分信息。第一部分用於存儲對象自身的運行時數據,如
注: 上面的幾個點,要結合和關聯其餘相關知識,理解會更加深刻一點。
如 哈希碼hashCode,對下面兩個問題若是你又本身的一些思考,歡迎留言探討!
一、重寫了equals 必需要重寫hashcode,思考一下,爲何?若是不重寫在使用HashMap的時候會有出現什麼問題?
二、HashMap中相同key存入數據不替換,而是進行疊加存儲,怎麼實現?
問題2提示:只要重寫了key的hashCode()和Map的put()方法,其實就能夠實現對於相同key下疊加存儲不一樣的value了。
第二部分是類型指針,即對象指向它的類元數據的指針,虛擬機經過指針來肯定這個對象是那個類的實例。(就如咱們上圖的箭頭,能夠簡單理解爲指針!)
說明:
(1)、並非全部的虛擬機實現都是必須在對象數據上保留類型指針,也就是查找對象的元數據並必定通過對象自己!
(2)、若是對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的的數據,由於虛擬機能夠經過普通Java對象的元數據肯定Java對象的大小,可是從數組的元數據卻沒法肯定數組的大小。
實例數據部分是對象真正存儲的有效信息,也就是程序代碼中定義的各類類型的字段內容。
不管是從父類繼承下來的,仍是在子類中定義的,都須要記錄起來。記錄的存儲順序會受到虛擬機分配策略參數和字段在Java源碼中的定義的順序相關。
對象的填充並非必然存在的,也沒有特別的含義,它僅僅起着佔位符的做用!因爲HotSpot VM的自動內存管理系統要求兌現的起始地址必須是8字節的整數倍,也就是說對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的整數倍,所以當對象實例數據部分沒有對齊時候,就須要填充來補全。
(類比記憶對齊填充,因爲審美的標準,有一些人天生就是俊俏的臉蛋和好的身材,不須要進行其餘的填充,有一些人可能有好看的臉蛋,可是某些地方和標準還差點意思,就須要填充來達到標準)
tips:字節
字節(byte)計算機裏用來存儲空間的基本計量單位。8個二進制位(bit)構成了一個字節(byte)即1byte=8bit。
認識了一個對象後,不能老是聊微信,也要約一下吃個飯啥的! 那在Java中創建了一個對象,那確定是要使用對象的。Java程序是若是找到具體的對象的呢?
在Java程序中須要經過棧上的reference數據來操做堆上的具體對象(如開篇的圖示,棧上面的引入指向堆中具體對象)。可是因爲Reference類型在Java虛擬機規範中只規定了一個指向對象的引用,並無定義這個引用應該經過何種方式去定位、訪問堆中的對象的具體位置,因此對象訪問方式也是取決於虛擬機實現而定的。
目前主流的訪問方式有使用句柄和直接指針兩種。
第一:句柄
使用句柄訪問,在Java對中將會劃分出一塊內存來做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象的實例數據與類型數據各自 的具體地址信息,如圖,
第二:直接指針
使用直接指針,在Java堆對象的佈局中就必須考慮若是放置訪問類型數組的相關信息,而reference中存儲的直接就是對象的地址,如圖:
兩種方式都各自優點,簡單總結:
句柄:最大的好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集移動對象是很是普通的行爲)時只會改變句柄中的實例數據指針,而Reference自己不須要修改。
直接指針:最大的好處就是速度更快,它節省一次指針定位的開銷,在Java中對象的訪問是很是頻繁的,所以能減小這類開銷對提升性能仍是很是客觀的。
虛擬機Hotspot使用的就是直接指針這種方式。可是其餘的語言和框架中使用句柄的狀況也很常見!
本文主要整理了Java中一個對象的建立,對象的內存佈局以及如何定位一個對象! 也讓咱們知道對象不是你想new就能夠new的,new出的對象想要「約」也是有不一樣方式的。
由於我也是在整理和學習中,若是文中內容有不對的地方,歡迎留言指出,謝謝!
《深刻理解Java虛擬機》
備註: 因爲本人能力有限,文中如有錯誤之處,歡迎指正。
謝謝你的閱讀,若是您以爲這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你天天開心愉快!