Tips
- 只要類持有對(duì)外部實(shí)力對(duì)象的引用, 垃圾回收機(jī)制就不會(huì)回收該對(duì)象
JVM中
堆和棧對(duì)比
存什么
-
棧內(nèi)存 存儲(chǔ)基本數(shù)據(jù)類型, 局部變量和方法調(diào)用和形參,棧分為java方法棧和native方法棧,
方法棧主要記錄的是方法運(yùn)行時(shí)的棧幀, 每執(zhí)行一個(gè)方法就會(huì)添加一個(gè)棧幀 ,方法返回后, 棧被清空, 堆等待GC回收
為單個(gè)函數(shù)分配的那部分??臻g叫做棧幀(StackFrame)
正在使用的??臻g叫做調(diào)用棧(CallStack)
在內(nèi)存中,棧是從高地址向低地址延伸的,即棧底對(duì)應(yīng)高地址,棧頂對(duì)應(yīng)低地址。java線程是不是開(kāi)兩個(gè)棧存放不同的棧幀看具體JDK, 比如Oracle JDK和OpenJDK就是一個(gè)調(diào)用棧存放兩種棧幀
堆內(nèi)存 存儲(chǔ)Java中的全部對(duì)象,this
int a[] = new int[4];
new int[] 存放在堆, int a[] 存放在棧
Double a[] = new Double[10000000];
Double qq = 3.1d;
for (int i = 0; i < a.length; i++) {
a[i] = qq.doubleValue();
}
a[i] = qq.doubleValue;
a[i] = Double.valueOf(qq);
a[i] = new Double(qq.doubleValue);
所以此double類的值存在堆
獨(dú)有/共享
棧內(nèi)存歸屬于線程, 每個(gè)線程都會(huì)有一個(gè)棧內(nèi)存, 其存儲(chǔ)的變量只能在其所屬的線程中可見(jiàn), 棧內(nèi)存可以理解成線程的私有內(nèi)存, 所以叫線程棧
堆內(nèi)存的對(duì)象, 對(duì)所有線程可見(jiàn), 可以被所有線程訪問(wèn)
異常
- 棧沒(méi)有空間存儲(chǔ)方法調(diào)用和局部變量, JVM會(huì)拋出Java.lang.StackOverFlowError, 純java代碼無(wú)法泄漏??臻g, 它完全被JVM掌控
- 堆沒(méi)有空間存儲(chǔ)對(duì)象, JVM會(huì)拋出java.lang.OutOfMemoryError
空間大小
- 棧內(nèi)存遠(yuǎn)小于堆內(nèi)存, ??赏ㄟ^(guò)jvm參數(shù) -XSS設(shè)置, 默認(rèn)隨著虛擬機(jī)和操作系統(tǒng)改變
執(zhí)行效率
- 棧是存取效率靈活, 僅次于寄存器, 棧數(shù)據(jù)可以共享, 但棧中的數(shù)據(jù)大小和生命周期固定, 缺乏靈活性
- 堆是自動(dòng)分配內(nèi)存大小, 生存期不用告訴編譯器, 等gc回收, 但是因?yàn)閯?dòng)態(tài)分配內(nèi)存, 存儲(chǔ)效率會(huì)比較慢
方法區(qū)
- 方法區(qū)存類信息, 靜態(tài)方法, 常量, 即時(shí)編譯器編譯后的代碼
GC(Garabage Collection)
指的是堆中數(shù)據(jù)的回收, 首先堆可以劃分為新生代和老年代

新生代繼續(xù)劃分為 Eden 和 Survivor Space(幸存區(qū)), Survivor Space 再被劃分成 From 和 To

新對(duì)象首先被創(chuàng)建在 Eden, (如果對(duì)象過(guò)大,如數(shù)組,則直接放入老年代). 在 GC 中, Eden 會(huì)被移入Survivor Space. 直到對(duì)象熬過(guò)一定的Minor GC的次數(shù), 會(huì)被移到老年代, 老年代用Major GC來(lái)清理
空間占比:
- 新生代 : 老年代 = 1:2
- Eden : From : To = 8:1:1
分代收集
新生代使用Minor GC, 老年代使用Major GC
Minor GC 和 Major GC 統(tǒng)稱為 Full GC
所有的Minor GC 會(huì)觸發(fā)全世界暫停 STW(stop-the-world), 停止應(yīng)用程序的線程, 當(dāng)然對(duì)于大多數(shù)應(yīng)用,停頓的延遲可以忽略不計(jì), 真相是大部分Eden區(qū)中的對(duì)象都能被認(rèn)為是垃圾,所以不會(huì)存放到Survivor Space.
現(xiàn)在很多的GC機(jī)制都會(huì)清理永久代(靜態(tài)方法區(qū))
- JVM并不強(qiáng)制要求GC實(shí)現(xiàn)哪種GC算法
純java代碼無(wú)法泄漏??臻g, 它完全被JVM掌控, 但如果有其他資源依附在java對(duì)象上, 如native memory(DirectByteBuffer), file(fileInputStream), 那么當(dāng)然自己關(guān)閉最合適
- 雖然有finalizer, PhantomReference之類的讓程序員向GC注冊(cè), 請(qǐng)求釋放資源,但是GC運(yùn)行時(shí)間不確定(因?yàn)槭且粭l單獨(dú)的線程), 還是自己釋放的好
可達(dá)性檢測(cè)
引用計(jì)數(shù): 一種在jdk1.2之前被使用的垃圾收集算法,我們需要了解其思想。其主要思想就是維護(hù)一個(gè)counter,當(dāng)counter為0的時(shí)候認(rèn)為對(duì)象沒(méi)有引用,可以被回收。缺點(diǎn)是無(wú)法處理循環(huán)引用。目前iOS開(kāi)發(fā)中的一個(gè)常見(jiàn)技術(shù)ARC(Automatic Reference Counting)也是采用類似的思路。在當(dāng)前的JVM中應(yīng)該是沒(méi)有被使用的。
-
根搜算法: gc root 根據(jù)引用關(guān)系來(lái)便利整個(gè)堆, 并標(biāo)記, 這稱之為Mark, 之后回收掉違背Mark的對(duì)象, 解決了「孤島效應(yīng)」, 這里的gc root 指的是:
- 虛擬機(jī)棧中引用的對(duì)象(棧幀中的本地變量表)
- 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中的常用變量的對(duì)象
- 本地方法棧中JNI 引用的對(duì)象
java減小GC開(kāi)銷 from
- 不要顯示調(diào)用System.gc()
此函數(shù)只是建議JVM進(jìn)行GC, 無(wú)法保證立馬執(zhí)行 - 減小臨時(shí)對(duì)象的使用
- 對(duì)象不用時(shí)顯示置為null
- 使用StringBuilder拼接字符串
String的擴(kuò)增是新建對(duì)象, 多次 + 會(huì)多次創(chuàng)建新對(duì)象 - 能用基本類型就不用對(duì)象
- 少用靜態(tài)
- 分散對(duì)象創(chuàng)建和刪除的時(shí)間
整理策略
- 復(fù)制
主要在新生代的回收上, 通過(guò)from 和 to 區(qū)的來(lái)回拷貝.對(duì)于新生成的對(duì)象, 頻繁的復(fù)制可以很快找到 那些不用的對(duì)象. -
標(biāo)記清除和標(biāo)記整理
主要在老生代的回收上, 通過(guò)根搜的標(biāo)記清除或者處理掉不用的對(duì)象.
整理的過(guò)程
清除的過(guò)程
清除會(huì)產(chǎn)生碎片,對(duì)內(nèi)存的利用不是很好, 但是不代表整理比清除好, 畢竟整理慢, 比如CMSGC就是使用清除而不是整理的
- 具體的垃圾收集器
- 新生代收集器:有Serial收集器、ParNew收集器、Parallel Scavenge收集器
-
老生代收集器:Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器
思考一下復(fù)制和標(biāo)記清除/整理的區(qū)別,為什么新生代要用復(fù)制?因?yàn)閷?duì)新生代來(lái)講,一次垃圾收集要回收掉絕大部分對(duì)象,我們通過(guò)冗余空間的辦法來(lái)加速整理過(guò)程(不冗余空間的整理操作要做swap,而冗余只需要做move)。同時(shí)可以記錄下每個(gè)對(duì)象的『年齡』從而優(yōu)化『晉升』操作使得中年對(duì)象不被錯(cuò)誤放到老年代。而反過(guò)來(lái)老年代偏穩(wěn)定,我們哪怕是用清除,也不會(huì)產(chǎn)生太多的碎片,并且整理的代價(jià)也并不會(huì)太大。
作者:納達(dá)丶無(wú)忌
鏈接:http://www.itdecent.cn/p/c9ac99b87d56
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
寄存器
在計(jì)算機(jī)領(lǐng)域,寄存器是CPU內(nèi)部的元件,它是有限存貯容量的高速存貯部件,可用來(lái)暫存指令、數(shù)據(jù)和地址。
寄存器分為通用寄存器和特殊寄存器。通用寄存器有 ax/bx/cx/dx/di/si,在大多數(shù)指令中可以任意選用,但也有一些規(guī)定某些指令只能用某個(gè)特定的「通用」寄存器;特殊寄存器有 bp/sp/ip 等,特殊寄存器均有特定用途。
在 Stack Frame 中,涉及到三種重要的特殊寄存器:
- bp ( base pointer ) 寄存器
- sp ( stack poinger ) 寄存器
- ip ( instruction pointer ) 寄存器
需要注意的是,不同架構(gòu)的CPU,寄存器名稱會(huì)添加不同的前綴來(lái)表示寄存器的大小。例如對(duì)于x86架構(gòu),字母「e」用作名稱前綴,表示寄存器大小為32位;對(duì)于x86_64架構(gòu),字母「r」用作名稱前綴,表示寄存器大小為64位。
舉例
- 下圖是linux 中一個(gè)進(jìn)程的虛擬內(nèi)存分布:
- 圖中0號(hào)地址在最下邊,越往上內(nèi)存地址越大。
以32位地址操作系統(tǒng)為例,一個(gè)進(jìn)程可擁有的虛擬內(nèi)存地址范圍為0-2^32。分為兩部分,一部分留給kernel使用(kernel virtual memory),剩下的是進(jìn)程本身使用, 即圖中的process virtual memory。
普通Java 程序使用的就是process virtual memory.
上圖中最頂端的一部分內(nèi)存叫做user stack. 這就是題目問(wèn)的 stack. 中間有個(gè) runtime heap。就是題目中的heap. 他們的名字和數(shù)據(jù)結(jié)構(gòu)里的stack 和 heap 幾乎每啥關(guān)系。
注意在上圖中,stack 是向下生長(zhǎng)的; heap是向上生長(zhǎng)的。
當(dāng)程序進(jìn)行函數(shù)調(diào)用時(shí),每個(gè)函數(shù)都在stack上有一個(gè) call frame。
比如對(duì)于以下程序,
public void foo(){
//do something...
println("haha"); // <<<=== 在這兒設(shè)置breakpoint 1
}
public void bar(){
foo();
}
main(){
bar();
println("hahaha"); // <<<=== 在這兒設(shè)置 breakpoint 2
}
當(dāng)程序運(yùn)行到breakponit1時(shí),user stack 里會(huì)有三個(gè)frame
|
| main 函數(shù)的 frame-------------------
|
| bar 函數(shù)的 frame-------------------<<<=== %ebp
|
| foo 函數(shù)的 frame------------------- <<<===%esp
其中 esp 和 ebp 都是寄存器。 esp 指向stack 的頂(因?yàn)閟tack 向下生長(zhǎng),esp會(huì)向下走); ebp 指向當(dāng)前frame的邊界。
當(dāng)程序繼續(xù)執(zhí)行到brekapoing 2的時(shí)候stack 大概是這樣的:
|
-------------------<<<=== %ebp
|
| main 函數(shù)的 frame------------------- <<<===%esp
也就是說(shuō)當(dāng)一個(gè)函數(shù)執(zhí)行結(jié)束后,它對(duì)應(yīng)的call frame就被銷毀了。(其實(shí)就是esp 和 ebp分別以東,但是內(nèi)存地址中的數(shù)據(jù)只有在下一次寫的時(shí)候才被覆蓋。)
說(shuō)了這么多,終于該說(shuō)什么東西放在stack 上什么東西放在heap 上了。
最直白的解釋:
public void foo(){
int i = 0; // <= i 的值存在stack上,foo()的call frame 里。
Object obj = new Object(); // object 對(duì)象本身存在heap 里, foo()的call frame 里存該對(duì)象的地址。
}
圖片引自CMU15-213的課件
https://www.cs.cmu.edu/~213/
作者:雷博
鏈接:https://www.zhihu.com/question/29833675/answer/45811216
來(lái)源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
數(shù)據(jù)結(jié)構(gòu)中
棧是先進(jìn)后出的結(jié)構(gòu)
參考


