該系列文章主要是記錄下自己暑假這段時(shí)間的學(xué)習(xí)筆記,暑期也在實(shí)習(xí),抽空學(xué)了很多,每個方面的知識我都會另起一篇博客去記錄,每篇頭部主要是另起博客的鏈接。
JavaSE集合(已寫)
JavaEE框架(未寫)
虛擬機(jī)JVM運(yùn)行時(shí)區(qū)域及垃圾回收(已寫)
Java并發(fā)(未寫)
計(jì)算機(jī)網(wǎng)絡(luò)(已寫)
八大經(jīng)典排序算法原理及實(shí)現(xiàn)(已寫)
一、JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)域
JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域可以分為:程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧、Java堆、方法區(qū)(包含運(yùn)行時(shí)常量池)
1、程序計(jì)數(shù)器:
可看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器
- 線程私有
2、Java 虛擬機(jī)棧
描述的是Java方法執(zhí)行的的內(nèi)存模型,每一個方法執(zhí)行的同時(shí)都會創(chuàng)建一個棧幀(棧幀詳解)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法的執(zhí)行到結(jié)束,都對應(yīng)著一個棧幀入棧到出棧
- 局部表量表:存放了編譯器可知的各種基本數(shù)據(jù)類型、對象引用
- 線程私有
- 如果線程請求的棧深度大于虛擬機(jī)所允許的深度會拋出StackOverflowError異常
- 如果虛擬機(jī)棧動態(tài)擴(kuò)展時(shí)無法申請到足夠的內(nèi)存,會拋出OutOfMemoryError錯誤
3、本地方法棧
與Java虛擬機(jī)棧的發(fā)揮的作用差不多,區(qū)別在于Java虛擬機(jī)棧是為Java方法服務(wù)、本地方法棧是為本地(Native)方法服務(wù)
- 線程私有
- 如果線程請求的棧深度大于虛擬機(jī)所允許的深度會拋出StackOverflowError異常
- 如果虛擬機(jī)棧動態(tài)擴(kuò)展時(shí)無法申請到足夠的內(nèi)存,會拋出OutOfMemoryError錯誤
4、Java 堆
堆在虛擬機(jī)啟動時(shí)創(chuàng)建,唯一的目的就是存放幾乎所有的對象實(shí)例。
- 線程共享
- 堆是垃圾收集的主要區(qū)域,常被稱為GC堆(Garbage Collected Heap)
- 可分為新生代、老年代:
- 再細(xì)致可以分為Eden、From Survivor、To Survivor空間:
- 堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法擴(kuò)展時(shí),拋出OutOfMemoryError
5、方法區(qū)
存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)常量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
- 線程共享
- 當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemooryError
6、運(yùn)行時(shí)常量池
是方法區(qū)的一部分.Class文件除了有類的版本、字段、方法、接口等描述信息外;還有一項(xiàng)常量池,用于存放編譯期生成的各種字面量和符號引用??,這些內(nèi)容再類被加載后進(jìn)入方法區(qū)的運(yùn)行常量池中存放。
- 運(yùn)行時(shí)常量池相對于Class文件常量池具有動態(tài)性,Java語言不要求常量一定只有編譯期才產(chǎn)生,String類的intern()方法可以在運(yùn)行期間將常量插入運(yùn)行時(shí)常量池
- 當(dāng)常量池?zé)o法再申請到內(nèi)存時(shí),會拋出OutOfMemoryError
7、直接內(nèi)存
并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是頻繁的使用也會造成OutOfMemooryError

二、對象的創(chuàng)建
大家都知道Java是一門面向?qū)ο蟮木幊陶Z言,在Java程序運(yùn)行過程中無時(shí)無刻都有對象被創(chuàng)建。語言層面上有通過 new方式 來創(chuàng)建、也有通過反射機(jī)制方式創(chuàng)建。在JVM中是怎么創(chuàng)建的呢?
- 類加載檢查:當(dāng)創(chuàng)建對象指令下達(dá)后,先檢查是否能在常量池中定位到該類的符號引用,并且檢查這個類是否已經(jīng)被加載、解析、和初始化過。若沒有,那必須先執(zhí)行類加載
- 為新生對象分配內(nèi)存:對象所需分配的大小在類加載完成后便可完全確定,分配內(nèi)存其相當(dāng)于在堆中劃分出一塊相應(yīng)的大小的內(nèi)存。在為對象分配內(nèi)存時(shí),需要考慮線程安全的問題
- 將分配到的內(nèi)存空間都初始化為0值(不包括對象頭):保證了對象的實(shí)例字段在Java代碼中可以不賦初值就直接使用
- 對對象進(jìn)行必要的設(shè)置:比如對象是哪個類的實(shí)例,如何才能找到元數(shù)據(jù)信息,對象個哈希碼、對象的GC分代信息等。這些信息都存儲在對象頭中
- 上述步驟之后,虛擬機(jī)層面上一個新對象已經(jīng)產(chǎn)生,但從Java層面上對象的創(chuàng)建才開始。其 <init>方法還沒有執(zhí)行,所有的字段都還未0
三、對象的訪問定位
建立對象都是為了使用它,在Java程序中,通過棧中的 reference 數(shù)據(jù)來操作堆上的具體對象
-
句柄訪問方式
-
直接指針訪問方式
四、JVM垃圾收集器與內(nèi)存分配策略
在了解JVM垃圾回收之前,需要知道3個問題:
- 哪些內(nèi)存需要釋放?
- 什么時(shí)候釋放?
- 如何回收?
首先要知道JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域中程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法都是隨線程而生、線程而亡,即線程私有的。在這3個區(qū)域的內(nèi)存分配和回收都具有確定性,在方法結(jié)束或線程結(jié)束時(shí)內(nèi)存就自動回收了,因此不用考慮垃圾回收。
在Java堆中和方法區(qū)則不一樣,一個接口的多個實(shí)現(xiàn)類需要的內(nèi)存不一樣,一個方法的多個分支需要的內(nèi)存也不一樣,只有在程序運(yùn)行期間才能知道,這部分內(nèi)存的分配和回收都是動態(tài)的。因此在這兩個區(qū)域考慮垃圾回收。
判斷對象是否存活
1.引用計(jì)數(shù)算法
給對象中添加一個引用計(jì)數(shù)器,每當(dāng)有一個地方引用它時(shí),計(jì)數(shù)器就加1;當(dāng)引用失效后,計(jì)數(shù)器就減1;當(dāng)計(jì)數(shù)器為0時(shí)的對象就是不可能再被使用的對象
- 優(yōu)勢:實(shí)現(xiàn)簡單、效率高
- 劣勢:難以解決對象之間相互循環(huán)引用的問題
2、可達(dá)性分析算法
通過一系列稱為"GC Roots"對象作為起始點(diǎn),從這些點(diǎn)向下搜索,搜索過程中走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何的引用鏈相連接時(shí),則證明該對象是不可用的
- 克服引用計(jì)數(shù)算法無法解決對象之間相互循環(huán)引用的問題

- GC Root 的對象包括:
- 虛擬機(jī)棧中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(Native方法)引用的對象
3、再次確認(rèn)對象的存亡
經(jīng)過上述任意一種算法,判斷出不可用的對象并非是非死不可的。一個不可用的對象再第經(jīng)過上述算法之后,將第一次標(biāo)記并且進(jìn)行篩選,篩選條件為該對象是否有必要執(zhí)行finelize()方法。
當(dāng)對象沒有覆蓋finelize()方法,或者finelize()方法已經(jīng)被虛擬機(jī)調(diào)用過,JVM將這兩種情況都視為沒有必要執(zhí)行
4、回收方法區(qū)
很多人認(rèn)為JVM方法區(qū)(永久代)中是沒有垃圾收集的,其實(shí)是有的,主要回收這兩部分:廢棄常量和無用的類
垃圾回收算法
1、標(biāo)記-清除算法
如同其名字一樣,算法分為標(biāo)記和清除兩個階段:首先標(biāo)記所有需要回收的對象,
在統(tǒng)一回被標(biāo)記的對象;其標(biāo)記過程和判斷對象的存活過程類似
- 不足:
- 標(biāo)記和清除兩個過程效率都低;
- 標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多會導(dǎo)致后面分配大內(nèi)存對象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前出發(fā)一個垃圾收集動作
2、復(fù)制算法
將可用內(nèi)存劃分為大小相等的兩塊,每次只使用其中的一塊;當(dāng)這一塊內(nèi)存快使用完了,就將還存活的對象復(fù)制到另外一塊上面,然后再把已使用過的那塊內(nèi)存空間一次清理掉。
- 解決了標(biāo)記清理的效率和內(nèi)存碎片問題
- 不足:將內(nèi)存縮小為原來的一半,付出的代價(jià)太高
新生代中解決復(fù)制算法的不足:
研究表明新生代中的對象98%是“朝生夕死”的,所以并不需要按1:1比例來劃分空間,而是將內(nèi)存劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,其比例為8:1:1,每次使用Eden空間和一塊Survivor空間,即新生代中可用內(nèi)存空間為90%。
若另一塊Survivor空間沒有足夠空間存放上一次新生代中收集下來存活的對象,這些對象將會直接被分配到老年代
3、標(biāo)記整理算法
- 復(fù)制算法在對象存活率較高時(shí)就要進(jìn)行較多復(fù)制操作,效率也會變低,更關(guān)鍵是會浪費(fèi)空間
針對老年代的特點(diǎn),提出標(biāo)記-整理算法,其標(biāo)記過程和標(biāo)記清除算法中一樣,而后續(xù)步驟則不一樣,而是讓所有存活的對象都向一端移動,然后直接清除掉該端邊界以外的內(nèi)存。
- 標(biāo)記-整理主要解決了內(nèi)存碎片問題
分代收集算法
將JVM堆分為新生代和老年代,這樣可以根據(jù)各個年代的特點(diǎn)采用最合適的收集算法
新生代中,每次垃圾收集時(shí)發(fā)現(xiàn)大批對象死去,只有少量對象存活,就選用復(fù)制算法,只需付出少量存活對象的復(fù)制成本就可以完成收集
老年代中,對象的存活率很高,無額為的空間為其進(jìn)行分配擔(dān)保,就必須使用標(biāo)記-清除或標(biāo)記-整理算法
簡單總結(jié):
- 兩個最基本的java回收算法:復(fù)制算法和標(biāo)記清理算法
- 復(fù)制算法:兩個區(qū)域A和B,初始對象在A,繼續(xù)存活的對象被轉(zhuǎn)移到B。此為新生代最常用的算法
- 標(biāo)記清理:一塊區(qū)域,標(biāo)記要回收的對象,然后回收,一定會出現(xiàn)碎片,那么引出
- 標(biāo)記-整理算法:多了碎片整理,整理出更大的內(nèi)存放更大的對象
- 兩個概念:新生代和年老代
- 新生代:初始對象,生命周期短的
- 永久代:長時(shí)間存在的對象
- 整個java的垃圾回收是新生代和年老代的協(xié)作,這種叫做分代回收。
垃圾收集器

**Serial **收集器是針對新生代的收集器,采用的是復(fù)制算法
Parallel New(并行)收集器,新生代采用復(fù)制算法,老年代采用標(biāo)記整理
Parallel Scavenge(并行)收集器,針對新生代,采用復(fù)制收集算法
Serial Old(串行)收集器,新生代采用復(fù)制,老年代采用標(biāo)記清理
Parallel Old(并行)收集器,針對老年代,標(biāo)記整理
CMS收集器,針對老年代,基于標(biāo)記清理
G1收集器,整體上是基于標(biāo)記清理,局部采用復(fù)制
綜上:新生代基本采用復(fù)制算法,老年代采用標(biāo)記整理算法。cms采用標(biāo)記清理。
JVM內(nèi)存分配參數(shù)
- Xmx:最大堆大小
- Xms:初始堆大小,即最小內(nèi)存值
- Xmn:年輕帶大小
- XXSurvivorRatio:年輕帶中 Eden:FromSurvivor:ToSurvivor=3:1:1

