Day7-堆,棧,方法區(qū)和GC

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)

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,795評(píng)論 11 349
  • JVM內(nèi)存模型Java虛擬機(jī)(Java Virtual Machine=JVM)的內(nèi)存空間分為五個(gè)部分,分別是: ...
    光劍書架上的書閱讀 2,774評(píng)論 2 26
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,051評(píng)論 3 83
  • 原文閱讀 前言 這段時(shí)間懈怠了,罪過(guò)! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,150評(píng)論 2 31
  • 今天下午第三節(jié)課,老師找了幾名同學(xué)去把校服拿來(lái)了。 當(dāng)校服被推進(jìn)門的時(shí)候,我們都瞪大了眼睛,我心想:這次校服怎么是...
    席振桓閱讀 347評(píng)論 0 0

友情鏈接更多精彩內(nèi)容