Java內(nèi)存結(jié)構(gòu)
java數(shù)據(jù)分為兩類,一類是基本數(shù)據(jù)類型,一類是引用數(shù)據(jù)類型核偿;講這兩類類型,先講一下
java運(yùn)行時(shí)需要分配內(nèi)存空間顽染,JVM內(nèi)存空間分為:
- 寄存器漾岳;
- 本地方法區(qū)轰绵;
- 方法區(qū);
- 棧內(nèi)存(stack)尼荆;
- 堆內(nèi)存(heap);
棧內(nèi)存:棧內(nèi)存首先是一片內(nèi)存區(qū)域左腔,存儲(chǔ)的都是局部變量,凡是定義在方法中的都是局部變量(方法外的是全局變量)捅儒,for循環(huán)內(nèi)部定義的也是局部變量液样,是先加載函數(shù)才能進(jìn)行局部變量的定義,所以方法先進(jìn)棧巧还,然后再定義變量蓄愁,變量有自己的作用域,一旦離開作用域狞悲,變量就會(huì)被釋放。棧內(nèi)存的更新速度很快妇斤,因?yàn)榫植孔兞康纳芷诙己芏獭?/p>
-
堆內(nèi)存:存儲(chǔ)的是數(shù)組和對象(其實(shí)數(shù)組就是對象)摇锋,凡是new建立的都是在堆中,堆中存放的都是實(shí)體(對象)站超,實(shí)體用于封裝數(shù)據(jù)荸恕,而且是封裝多個(gè)(實(shí)體的多個(gè)屬性),如果一個(gè)數(shù)據(jù)消失死相,這個(gè)實(shí)體也沒有消失融求,還可以用,所以堆是不會(huì)隨時(shí)釋放的算撮,但是棧不一樣生宛,棧里存放的都是單個(gè)變量,變量被釋放了肮柜,那就沒有了陷舅。堆里的實(shí)體雖然不會(huì)被釋放,但是會(huì)被當(dāng)成垃圾审洞,Java有垃圾回收機(jī)制不定時(shí)的收取莱睁。
Jvm在堆中存儲(chǔ)實(shí)際對象的內(nèi)容如下:
方法區(qū)是靜態(tài)分配的,編譯器將變量綁定在某個(gè)存儲(chǔ)位置上芒澜,而且這些綁定不會(huì)在運(yùn)行時(shí)改變仰剿。 常數(shù)池,源代碼中的命名常量痴晦、String 常量和 static 變量保存在方法區(qū)南吮。
一.基本數(shù)據(jù)類型
分別為int(4)、float(4)阅酪、double(8)旨袒、long(8)汁针、char(2)、short(2)砚尽、byte(1)施无、boolean(1)(boolean 類型比較特別可能只占一個(gè) bit,多個(gè) boolean 可能共同占用一個(gè)字節(jié))必孤。其值是直接存儲(chǔ)在棧內(nèi)存中猾骡。
二.引用數(shù)據(jù)類型
實(shí)際對象的內(nèi)存是分配在堆內(nèi)存當(dāng)中,棧內(nèi)存存儲(chǔ)的是堆內(nèi)存的分配的內(nèi)存地址敷搪。
eg 主函數(shù)里的語句 int [] arr=new int [3];在內(nèi)存中是怎么被定義的:
主函數(shù)先進(jìn)棧兴想,在棧中定義一個(gè)變量arr,接下來為arr賦值,但是右邊不是一個(gè)具體值赡勘,是一個(gè)實(shí)體嫂便。實(shí)體創(chuàng)建在堆里,在堆里首先通過new關(guān)鍵字開辟一個(gè)空間闸与,內(nèi)存在存儲(chǔ)數(shù)據(jù)的時(shí)候都是通過地址來體現(xiàn)的毙替,地址是一塊連續(xù)的二進(jìn)制,然后給這個(gè)實(shí)體分配一個(gè)內(nèi)存地址践樱。數(shù)組都是有一個(gè)索引厂画,數(shù)組這個(gè)實(shí)體在堆內(nèi)存中產(chǎn)生之后每一個(gè)空間都會(huì)進(jìn)行默認(rèn)的初始化(這是堆內(nèi)存的特點(diǎn),未初始化的數(shù)據(jù)是不能用的拷邢,但在堆里是可以用的袱院,因?yàn)槌跏蓟^了,但是在棧里沒有)瞭稼,不同的類型初始化的值不一樣忽洛。所以堆和棧里就創(chuàng)建了變量和實(shí)體:
給堆分配了一個(gè)地址,把堆的地址賦給arr环肘,arr就通過地址指向了數(shù)組脐瑰。所以arr想操縱數(shù)組時(shí),就通過地址廷臼,而不是直接把實(shí)體都賦給它苍在。這種我們不再叫他基本數(shù)據(jù)類型,而叫引用數(shù)據(jù)類型荠商。稱為arr引用了堆內(nèi)存當(dāng)中的實(shí)體寂恬。
這里引申一下,講一下垃圾的產(chǎn)生
如果當(dāng)int [] arr=null; arr不做任何指向莱没,null的作用就是取消引用數(shù)據(jù)類型的指向初肉。
當(dāng)一個(gè)實(shí)體,沒有引用數(shù)據(jù)類型指向的時(shí)候饰躲,它在堆內(nèi)存中不會(huì)被釋放牙咏,而被當(dāng)做一個(gè)垃圾臼隔,在不定時(shí)的時(shí)間內(nèi)自動(dòng)回收,因?yàn)镴ava有一個(gè)自動(dòng)回收機(jī)制妄壶,(而c++沒有摔握,需要程序員手動(dòng)回收,如果不回收就越堆越多丁寄,直到撐滿內(nèi)存溢出氨淌,所以Java在內(nèi)存管理上優(yōu)于c++)。自動(dòng)回收機(jī)制(程序)自動(dòng)監(jiān)測堆里是否有垃圾伊磺,如果有盛正,就會(huì)自動(dòng)的做垃圾回收的動(dòng)作,但是什么時(shí)候收不一定屑埋。
1豪筝、基礎(chǔ)數(shù)據(jù)類型直接在棧空間分配;
2摘能、方法的形式參數(shù)壤蚜,直接在棧空間分配徊哑,當(dāng)方法調(diào)用完成后從棧空間回收;
3聪富、引用數(shù)據(jù)類型莺丑,需要用 new 來創(chuàng)建,既在椂章空間分配一個(gè)地址空間梢莽,又在堆空間分配對象的類變量;
4、方法的引用參數(shù)奸披,在椈杳空間分配一個(gè)地址空間,并指向堆空間的對象區(qū)阵面,當(dāng)方法調(diào)用完后從椙峋郑空間回收;
5、局部變量 new 出來時(shí)样刷,在椔仄耍空間和堆空間中分配空間,當(dāng)局部變量生命周期結(jié)束后置鼻,椪蛞空間立刻被回收, 堆空間區(qū)域等待 GC 回收;
6箕母、方法調(diào)用時(shí)傳入的實(shí)際參數(shù)储藐,先在椌慵茫空間分配,在方法調(diào)用完成后從椄撇空間釋放;
7蛛碌、字符串常量在 DATA 區(qū)域分配 ,this 在堆空間分配;
8肺缕、數(shù)組既在椬笠剑空間分配數(shù)組名稱, 又在堆空間分配數(shù)組實(shí)際的大型尽浮梢!
在拓展講一下,拷貝
Person p = new Person(age=23, name="zhang");
有一個(gè)p的Person引用數(shù)據(jù)類型彤路,new 操作符的本意是分配內(nèi)存秕硝。程序執(zhí)行到 new 操作符時(shí), 首先去看 new 操作符后面的類型洲尊,因?yàn)橹懒祟愋停?才能知道要分配多大的內(nèi)存空間远豺。分配完內(nèi)存之后,再調(diào)用構(gòu)造函數(shù)坞嘀,填充對象的各個(gè)域躯护,這一步叫做對象的初始化, 構(gòu)造方法返回后丽涩,一個(gè)對象創(chuàng)建完畢棺滞,可以把他的引用(地址)發(fā)布到外部,在外部就可以使用這個(gè)引用操縱這個(gè)對 象矢渊。
1.引用復(fù)制
Person p1 = p;
地址值是相同的继准,p,p1指向同一個(gè)對象
2.對象復(fù)制
Person p1 = (Person) p.clone();
其中對象拷貝分為深拷貝和淺拷貝
Person 中有兩個(gè)成員變量,分別是 name 和 age矮男, name 是 String 類型移必, age 是 int 類型,由于 age 是基本數(shù)據(jù)類型,那么對它的拷貝沒有什么疑議毡鉴,直接將一個(gè) 4 字節(jié)的整數(shù)值拷貝過來就行崔泵。但是 name 是 String 類型的, 它只是一個(gè)引用猪瞬, 指向一個(gè)真正的 String 對象管削,那么對它的拷貝有兩種方式: 直接將原對象中 的 name 的引用值拷貝給新對象的 name 字段,或者是根據(jù)原 Person 對象中的 name 指向的字符串對象創(chuàng)建一個(gè)新 的相同的字符串對象撑螺,將這個(gè)新字符串對象的引用賦給新拷貝的 Person 對象的 name 字段含思。這兩種拷貝方式分別叫 做淺拷貝和深拷貝。
對象:對象是類的一個(gè)實(shí)例,有狀態(tài)和行為含潘。例如饲做,一條狗是一個(gè)對象,它的狀態(tài)有:顏色遏弱、名字盆均、品種;行為有:搖尾巴漱逸、叫泪姨、吃等。
類:類是一個(gè)模板饰抒,它描述一類對象的行為和狀態(tài)肮砾。
2.1.Java 中引用類型
Java 中對象的引用分為四種級別,這四種級別由高到低依次為:強(qiáng)引用袋坑、軟引用仗处、弱引用和虛引用。
如果一個(gè)對象被被人擁有強(qiáng)引用枣宫,那么垃圾回收器絕不 會(huì)回收它婆誓。當(dāng)內(nèi)存空間不足,Java 虛擬機(jī)寧愿拋出 OutOfMemoryError 錯(cuò)誤也颤,使程序異常終止洋幻,也不會(huì)靠隨意 回收具有強(qiáng)引用的對象來解決內(nèi)存不足問題。
Java 的對象是位于 heap 中的翅娶,heap 中對象有強(qiáng)可及對象文留、軟可及對象、弱可及對象故觅、虛可及對象和不可到 達(dá)對象。應(yīng)用的強(qiáng)弱順序是強(qiáng)渠啊、軟输吏、弱、和虛替蛉。對于對象是屬于哪種可及的對象贯溅,由他的最強(qiáng)的引用決定。
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2);//false
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2);//true
在Java的實(shí)現(xiàn)中躲查,new出來的String對象一般是放在堆中的它浅。如果是 String s ="xxx"; 這種,那就是放在常量池中.JDK6將常量池放在方法區(qū)中。但是從JDK7開始, 常量池的實(shí)現(xiàn) 已經(jīng)從方法區(qū)中移出來放到 堆內(nèi)存里面了镣煮。
類初始化
java類的生命周期:
指一個(gè)class文件從加載到卸載的全過程姐霍,類的完整生命周期包括7個(gè)部分:加載——驗(yàn)證——準(zhǔn)備——解析——初始化——使用——卸載,如下圖所示,其中镊折,驗(yàn)證——準(zhǔn)備——解析 稱為連接階段胯府,除了解析外,其他階段是順序發(fā)生的恨胚,而解析可以與這些階段交叉進(jìn)行骂因,因?yàn)镴ava支持動(dòng)態(tài)綁定(晚期綁定),需要運(yùn)行時(shí)才能確定具體類型赃泡;在使用階段實(shí)例化對象
1.類初始化
類的初始化:是完成程序執(zhí)行前的準(zhǔn)備工作寒波。在這個(gè)階段,靜態(tài)的(變量升熊,方法俄烁,代碼塊)會(huì)被執(zhí)行。同時(shí)在會(huì)開辟一塊存儲(chǔ)空間用來存放靜態(tài)的數(shù)據(jù)僚碎。初始化只在類加載的時(shí)候執(zhí)行一次猴娩。
類的實(shí)例化:是指創(chuàng)建一個(gè)對象的過程。這個(gè)過程中會(huì)在堆中開辟內(nèi)存勺阐,將一些非靜態(tài)的方法卷中,變量存放在里面。在程序執(zhí)行的過程中渊抽,可以創(chuàng)建多個(gè)對象蟆豫,既多次實(shí)例化。每次實(shí)例化都會(huì)開辟一塊新的內(nèi)存懒闷。即new操作
2.類初始化順序
java類初始化原則是
- 靜態(tài)對象(變量)先于非靜態(tài)對象(變量)初始化十减。其中靜態(tài)對象(變量)只初始化一次,因?yàn)閟tatic在jvm中只有一塊區(qū)域存儲(chǔ)愤估,方法區(qū)(Method Area)帮辟,
他之所以被稱為靜態(tài)是因?yàn)閺某绦騽?chuàng)建到死亡他的地址值都不會(huì)改變,他只在class類對象初次加載時(shí)初始化玩焰,因此static只需要初始化一次由驹,而非靜態(tài)對象(變量)可能會(huì)初始化很多次。類中存在初始化語句(如 static 變量和 static 塊)昔园,那就依次執(zhí)行這些初始化語句蔓榄。- 如果類之間存在繼承關(guān)系,那么父類優(yōu)先于子類進(jìn)行初始化默刚。
- 按照成員變量的定義順序進(jìn)行初始化甥郑。即使變量定義散布于方法之中,他們依然在任何方法(包括構(gòu)造函數(shù))被調(diào)用前先初始化
Java程序初始化的順序:父類靜態(tài)變量 -> 父類靜態(tài)代碼塊 -> 子類靜態(tài)變量 -> 子類靜態(tài)代碼塊 -> 父類非靜態(tài)變量 -> 父類非靜態(tài)代碼塊 -> 父類構(gòu)造器 -> 子類非靜態(tài)變量 -> 子類非靜態(tài)代碼塊 -> 子類構(gòu)造器荤西。
Kotlin:
- init關(guān)鍵字==>java非靜態(tài)方法塊
- companion object伴生對象==>java靜態(tài)方法
- 伴生對象中的init方法==>java靜態(tài)代碼塊
Java調(diào)用靜態(tài)方法需要類裝載還是初始化?
- 結(jié)論:Java調(diào)用靜態(tài)方法時(shí)會(huì)對類進(jìn)行裝載澜搅、連接和初始化
- 原因:Java類的加載方式是按需加載伍俘,遇到new、getstatic店展、putstatic或invokestatic這4條字節(jié)碼指令時(shí)养篓,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化赂蕴。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實(shí)例化對象的時(shí)候柳弄、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候概说,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候碧注。
類什么時(shí)候被初始化?
1)創(chuàng)建類的實(shí)例糖赔,也就是 new 一個(gè)對象
2)訪問某個(gè)類或接口的靜態(tài)變量萍丐,或者對該靜態(tài)變量賦值
3)調(diào)用類的靜態(tài)方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一個(gè)類的子類(會(huì)首先初始化子類的父類)
6)JVM 啟動(dòng)時(shí)標(biāo)明的啟動(dòng)類,即文件名和類名相同的那個(gè)類