Tips
- 只要類持有對外部實力對象的引用, 垃圾回收機制就不會回收該對象
JVM中
堆和棧對比
存什么
-
棧內(nèi)存 存儲基本數(shù)據(jù)類型, 局部變量和方法調(diào)用和形參,棧分為java方法棧和native方法棧,
方法棧主要記錄的是方法運行時的棧幀, 每執(zhí)行一個方法就會添加一個棧幀 ,方法返回后, 棧被清空, 堆等待GC回收
為單個函數(shù)分配的那部分棧空間叫做棧幀(StackFrame)
正在使用的棧空間叫做調(diào)用棧(CallStack)
在內(nèi)存中,棧是從高地址向低地址延伸的酱固,即棧底對應高地址浅妆,棧頂對應低地址蒿偎。java線程是不是開兩個棧存放不同的棧幀看具體JDK, 比如Oracle JDK和OpenJDK就是一個調(diào)用棧存放兩種棧幀
堆內(nèi)存 存儲Java中的全部對象,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類的值存在堆
獨有/共享
棧內(nèi)存歸屬于線程, 每個線程都會有一個棧內(nèi)存, 其存儲的變量只能在其所屬的線程中可見, 棧內(nèi)存可以理解成線程的私有內(nèi)存, 所以叫線程棧
堆內(nèi)存的對象, 對所有線程可見, 可以被所有線程訪問
異常
- 棧沒有空間存儲方法調(diào)用和局部變量, JVM會拋出Java.lang.StackOverFlowError, 純java代碼無法泄漏椏谟瑁空間, 它完全被JVM掌控
- 堆沒有空間存儲對象, JVM會拋出java.lang.OutOfMemoryError
空間大小
- 棧內(nèi)存遠小于堆內(nèi)存, 棧可通過jvm參數(shù) -XSS設(shè)置, 默認隨著虛擬機和操作系統(tǒng)改變
執(zhí)行效率
- 棧是存取效率靈活, 僅次于寄存器, 棧數(shù)據(jù)可以共享, 但棧中的數(shù)據(jù)大小和生命周期固定, 缺乏靈活性
- 堆是自動分配內(nèi)存大小, 生存期不用告訴編譯器, 等gc回收, 但是因為動態(tài)分配內(nèi)存, 存儲效率會比較慢
方法區(qū)
- 方法區(qū)存類信息, 靜態(tài)方法, 常量, 即時編譯器編譯后的代碼
GC(Garabage Collection)
指的是堆中數(shù)據(jù)的回收, 首先堆可以劃分為新生代和老年代
新生代繼續(xù)劃分為 Eden 和 Survivor Space(幸存區(qū)), Survivor Space 再被劃分成 From 和 To
新對象首先被創(chuàng)建在 Eden, (如果對象過大因篇,如數(shù)組,則直接放入老年代). 在 GC 中, Eden 會被移入Survivor Space. 直到對象熬過一定的Minor GC的次數(shù), 會被移到老年代, 老年代用Major GC來清理
空間占比:
- 新生代 : 老年代 = 1:2
- Eden : From : To = 8:1:1
分代收集
新生代使用Minor GC, 老年代使用Major GC
Minor GC 和 Major GC 統(tǒng)稱為 Full GC
所有的Minor GC 會觸發(fā)全世界暫停 STW(stop-the-world), 停止應用程序的線程, 當然對于大多數(shù)應用笔横,停頓的延遲可以忽略不計, 真相是大部分Eden區(qū)中的對象都能被認為是垃圾竞滓,所以不會存放到Survivor Space.
現(xiàn)在很多的GC機制都會清理永久代(靜態(tài)方法區(qū))
- JVM并不強制要求GC實現(xiàn)哪種GC算法
純java代碼無法泄漏棧空間, 它完全被JVM掌控, 但如果有其他資源依附在java對象上, 如native memory(DirectByteBuffer), file(fileInputStream), 那么當然自己關(guān)閉最合適
- 雖然有finalizer, PhantomReference之類的讓程序員向GC注冊, 請求釋放資源,但是GC運行時間不確定(因為是一條單獨的線程), 還是自己釋放的好
可達性檢測
引用計數(shù): 一種在jdk1.2之前被使用的垃圾收集算法吹缔,我們需要了解其思想商佑。其主要思想就是維護一個counter,當counter為0的時候認為對象沒有引用厢塘,可以被回收茶没。缺點是無法處理循環(huán)引用肌幽。目前iOS開發(fā)中的一個常見技術(shù)ARC(Automatic Reference Counting)也是采用類似的思路。在當前的JVM中應該是沒有被使用的抓半。
-
根搜算法: gc root 根據(jù)引用關(guān)系來便利整個堆, 并標記, 這稱之為Mark, 之后回收掉違背Mark的對象, 解決了「孤島效應」, 這里的gc root 指的是:
- 虛擬機棧中引用的對象(棧幀中的本地變量表)
- 方法區(qū)中的類靜態(tài)屬性引用的對象
- 方法區(qū)中的常用變量的對象
- 本地方法棧中JNI 引用的對象
java減小GC開銷 from
- 不要顯示調(diào)用System.gc()
此函數(shù)只是建議JVM進行GC, 無法保證立馬執(zhí)行 - 減小臨時對象的使用
- 對象不用時顯示置為null
- 使用StringBuilder拼接字符串
String的擴增是新建對象, 多次 + 會多次創(chuàng)建新對象 - 能用基本類型就不用對象
- 少用靜態(tài)
- 分散對象創(chuàng)建和刪除的時間
整理策略
- 復制
主要在新生代的回收上, 通過from 和 to 區(qū)的來回拷貝.對于新生成的對象, 頻繁的復制可以很快找到 那些不用的對象. -
標記清除和標記整理
主要在老生代的回收上, 通過根搜的標記清除或者處理掉不用的對象.
整理的過程
清除的過程
清除會產(chǎn)生碎片喂急,對內(nèi)存的利用不是很好, 但是不代表整理比清除好, 畢竟整理慢, 比如CMSGC就是使用清除而不是整理的
- 具體的垃圾收集器
- 新生代收集器:有Serial收集器、ParNew收集器笛求、Parallel Scavenge收集器
-
老生代收集器:Serial Old收集器廊移、Parallel Old收集器、CMS收集器涣易、G1收集器
思考一下復制和標記清除/整理的區(qū)別画机,為什么新生代要用復制?因為對新生代來講新症,一次垃圾收集要回收掉絕大部分對象步氏,我們通過冗余空間的辦法來加速整理過程(不冗余空間的整理操作要做swap,而冗余只需要做move)徒爹。同時可以記錄下每個對象的『年齡』從而優(yōu)化『晉升』操作使得中年對象不被錯誤放到老年代荚醒。而反過來老年代偏穩(wěn)定,我們哪怕是用清除隆嗅,也不會產(chǎn)生太多的碎片界阁,并且整理的代價也并不會太大。
作者:納達丶無忌
鏈接:http://www.reibang.com/p/c9ac99b87d56
來源:簡書
著作權(quán)歸作者所有胖喳。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)泡躯,非商業(yè)轉(zhuǎn)載請注明出處。
寄存器
在計算機領(lǐng)域丽焊,寄存器是CPU內(nèi)部的元件较剃,它是有限存貯容量的高速存貯部件,可用來暫存指令技健、數(shù)據(jù)和地址写穴。
寄存器分為通用寄存器和特殊寄存器。通用寄存器有 ax/bx/cx/dx/di/si雌贱,在大多數(shù)指令中可以任意選用啊送,但也有一些規(guī)定某些指令只能用某個特定的「通用」寄存器;特殊寄存器有 bp/sp/ip 等欣孤,特殊寄存器均有特定用途馋没。
在 Stack Frame 中,涉及到三種重要的特殊寄存器:
- bp ( base pointer ) 寄存器
- sp ( stack poinger ) 寄存器
- ip ( instruction pointer ) 寄存器
需要注意的是导街,不同架構(gòu)的CPU披泪,寄存器名稱會添加不同的前綴來表示寄存器的大小。例如對于x86架構(gòu)搬瑰,字母「e」用作名稱前綴款票,表示寄存器大小為32位控硼;對于x86_64架構(gòu),字母「r」用作名稱前綴艾少,表示寄存器大小為64位卡乾。
舉例
- 下圖是linux 中一個進程的虛擬內(nèi)存分布:
- 圖中0號地址在最下邊,越往上內(nèi)存地址越大缚够。
以32位地址操作系統(tǒng)為例幔妨,一個進程可擁有的虛擬內(nèi)存地址范圍為0-2^32。分為兩部分谍椅,一部分留給kernel使用(kernel virtual memory)误堡,剩下的是進程本身使用, 即圖中的process virtual memory雏吭。
普通Java 程序使用的就是process virtual memory.
上圖中最頂端的一部分內(nèi)存叫做user stack. 這就是題目問的 stack. 中間有個 runtime heap锁施。就是題目中的heap. 他們的名字和數(shù)據(jù)結(jié)構(gòu)里的stack 和 heap 幾乎每啥關(guān)系。
注意在上圖中杖们,stack 是向下生長的; heap是向上生長的悉抵。
當程序進行函數(shù)調(diào)用時,每個函數(shù)都在stack上有一個 call frame摘完。
比如對于以下程序姥饰,
public void foo(){
//do something...
println("haha"); // <<<=== 在這兒設(shè)置breakpoint 1
}
public void bar(){
foo();
}
main(){
bar();
println("hahaha"); // <<<=== 在這兒設(shè)置 breakpoint 2
}
當程序運行到breakponit1時,user stack 里會有三個frame
|
| main 函數(shù)的 frame-------------------
|
| bar 函數(shù)的 frame-------------------<<<=== %ebp
|
| foo 函數(shù)的 frame------------------- <<<===%esp
其中 esp 和 ebp 都是寄存器孝治。 esp 指向stack 的頂(因為stack 向下生長列粪,esp會向下走); ebp 指向當前frame的邊界。
當程序繼續(xù)執(zhí)行到brekapoing 2的時候stack 大概是這樣的:
|
-------------------<<<=== %ebp
|
| main 函數(shù)的 frame------------------- <<<===%esp
也就是說當一個函數(shù)執(zhí)行結(jié)束后谈飒,它對應的call frame就被銷毀了篱竭。(其實就是esp 和 ebp分別以東,但是內(nèi)存地址中的數(shù)據(jù)只有在下一次寫的時候才被覆蓋步绸。)
說了這么多,終于該說什么東西放在stack 上什么東西放在heap 上了吃媒。
最直白的解釋:
public void foo(){
int i = 0; // <= i 的值存在stack上瓤介,foo()的call frame 里。
Object obj = new Object(); // object 對象本身存在heap 里赘那, foo()的call frame 里存該對象的地址刑桑。
}
圖片引自CMU15-213的課件
https://www.cs.cmu.edu/~213/
作者:雷博
鏈接:https://www.zhihu.com/question/29833675/answer/45811216
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)募舟,非商業(yè)轉(zhuǎn)載請注明出處祠斧。
數(shù)據(jù)結(jié)構(gòu)中
棧是先進后出的結(jié)構(gòu)
參考