當(dāng)前位置:首頁(yè) >  站長(zhǎng) >  編程技術(shù) >  正文

JVM內(nèi)存管理和垃圾回收

 2020-10-26 11:17  來(lái)源: 博客園   我來(lái)投稿 撤稿糾錯(cuò)

  域名預(yù)訂/競(jìng)價(jià),好“米”不錯(cuò)過(guò)

無(wú)論對(duì)于Java程序員還是大數(shù)據(jù)研發(fā)人員,JVM是必須掌握的技能之一。既是面試中經(jīng)常問(wèn)的問(wèn)題,也是在實(shí)際業(yè)務(wù)中對(duì)程序進(jìn)行調(diào)優(yōu)、排查類(lèi)似于內(nèi)存溢出、棧溢出、內(nèi)存泄漏等問(wèn)題的關(guān)鍵。筆者將按下圖分多篇文章詳細(xì)闡述JVM:

本篇文章主要敘述JVM內(nèi)存管理、直接內(nèi)存、垃圾回收和常見(jiàn)的垃圾回收算法:

運(yùn)行時(shí)數(shù)據(jù)區(qū)域

JVM在執(zhí)行一些基于JVM運(yùn)行的程序,典型的如Java程序、Scala程序時(shí),會(huì)把它所管理的內(nèi)存劃分為多個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域有各個(gè)的作用、創(chuàng)建和銷(xiāo)毀時(shí)間,有的區(qū)域生命周期依賴于用戶線程的啟動(dòng)和結(jié)束,有些區(qū)域則隨著虛擬機(jī)的啟動(dòng)而存在,下圖展示了JVM在運(yùn)行時(shí)的數(shù)據(jù)區(qū)域劃分:

1. 方法區(qū)

方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,主要用于存放一些"自始至終都不會(huì)變化"的東西,比如final定義的常量、類(lèi)的信息(class實(shí)例)、靜態(tài)變量等、方法信息。因?yàn)檫@些東西一旦被加載,是幾乎不會(huì)被GC的,所以方法區(qū)又被稱(chēng)為永久代(注意一點(diǎn),二者本質(zhì)并不等價(jià))。

方法區(qū)有一部分叫常量池,用于存儲(chǔ)編譯期生成的一些字面變量、符號(hào)引用以及一些運(yùn)行時(shí)產(chǎn)生的常量(如String常量池)。方法區(qū)中的靜態(tài)區(qū)用于存放類(lèi)變量、靜態(tài)塊等。

方法區(qū)又稱(chēng)非堆,是有大小限制的,如果方法區(qū)使用內(nèi)存超過(guò)了分配的大小,就會(huì)報(bào)類(lèi)似OutOfMemory: PermGen Space的錯(cuò)誤。

2. Java虛擬機(jī)棧

Java 虛擬機(jī)棧是線程私有的,它的生命周期與線程相同,為虛擬機(jī)執(zhí)行Java方法即字節(jié)碼服務(wù),是描述Java方法執(zhí)行時(shí)的內(nèi)存模型。

每個(gè)方法執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表(比如編譯期可知的基本數(shù)據(jù)類(lèi)型、對(duì)象引用等)、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法被調(diào)用至執(zhí)行完成的過(guò)程,對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程。

如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將會(huì)報(bào)StackOverFlowError;如果虛擬機(jī)棧無(wú)法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)報(bào)OutOfMemoryError。

調(diào)整虛擬機(jī)棧大小的方式:-Xss。

3. 本地方法棧

本地方法棧為使用的到Native方法服務(wù),本地方法接口都會(huì)使用某種本地方法棧。

當(dāng)線程調(diào)用Java方法時(shí),虛擬機(jī)會(huì)創(chuàng)建一個(gè)新的棧幀并壓入Java棧。然而,當(dāng)它調(diào)用的是本地方法時(shí),虛擬機(jī)會(huì)保持Java棧不變,不會(huì)在線程的Java棧中壓入新的棧幀,而是動(dòng)態(tài)連接并直接調(diào)用指定的本地方法。

4. 堆

堆是JVM管理內(nèi)存中最大的一塊區(qū)域,由Java線程共享,主要用來(lái)存儲(chǔ)new出來(lái)的對(duì)象和數(shù)組,并且這塊區(qū)域隨著虛擬機(jī)的啟動(dòng)而創(chuàng)建。堆可以處于邏輯上連續(xù)但物理上不連續(xù)的內(nèi)存空間中。

堆是垃圾回收器管理的主要區(qū)域,可以細(xì)分為新生代和老年代,新生代又劃分為eden區(qū),from survivor區(qū)、to survivor區(qū)。

對(duì)象在被創(chuàng)建時(shí),首先在新生代進(jìn)行分配,eden區(qū)存放新生成的對(duì)象,兩個(gè)survivor區(qū)用來(lái)存放新生代中每次垃圾回收后依然存活下來(lái)的對(duì)象。但是當(dāng)創(chuàng)建新創(chuàng)建的對(duì)象非常大,該對(duì)象會(huì)直接進(jìn)入老年代。

5. 程序計(jì)數(shù)器

程序計(jì)數(shù)器是線程私有的即每個(gè)線程都會(huì)有自己的程序計(jì)數(shù)器,用來(lái)記錄線程執(zhí)行的字節(jié)碼位置,是一個(gè)沒(méi)有OOM的區(qū)域。

直接內(nèi)存

直接內(nèi)存(direct memory)不屬于JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,屬于堆外內(nèi)存,會(huì)被頻繁使用,因此在設(shè)置各個(gè)內(nèi)存范圍時(shí)要留出一部分物理內(nèi)存,否則也容易拋出OutOfMemoryError。

垃圾收集

垃圾收集即GC,是JVM進(jìn)行內(nèi)存回收的處理過(guò)程。

開(kāi)發(fā)人員更多的是關(guān)注業(yè)務(wù)需求的實(shí)現(xiàn),而內(nèi)存管理是交由JVM完成的,如果不進(jìn)行或者錯(cuò)誤的進(jìn)行垃圾回收會(huì)導(dǎo)致程序不穩(wěn)定甚至崩潰。Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過(guò)作用域等從而達(dá)到自動(dòng)回收內(nèi)存的目的,可以有效防止內(nèi)存泄露,有效的使用可用內(nèi)存。

GC主要分為3種:minor GC、major GC和full GC。

minor GC是發(fā)生在新生代的,minor GC是發(fā)生在老年代的。對(duì)于full GC出發(fā)的原因則比較多,比如老年代空間不足,它會(huì)出發(fā)stop world,處理不好往往會(huì)影響整個(gè)程序的穩(wěn)定性嚴(yán)重會(huì)導(dǎo)致系統(tǒng)不可用,需要特別注意。

常見(jiàn)的垃圾回收算法

1. 標(biāo)記清除算法

首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。存在如下兩個(gè)缺點(diǎn):1.效率低需要先對(duì)要回收的對(duì)象進(jìn)行標(biāo)記,然后再統(tǒng)一清除,然而標(biāo)記和清除兩個(gè)過(guò)程效率都很低下。2.內(nèi)存碎片問(wèn)題標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作,影響性能。

2. 復(fù)制算法

先將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)使用的這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。

優(yōu)點(diǎn):這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。

缺點(diǎn):不適合對(duì)象存活率較高的場(chǎng)景,因?yàn)檫@種場(chǎng)景要進(jìn)行較多的復(fù)制操作影響效率;實(shí)際可用內(nèi)存變?yōu)榉峙鋬?nèi)存的一半,因?yàn)槊看沃皇褂闷渲械囊话雰?nèi)存。

3. 標(biāo)記整理算法

先標(biāo)記(標(biāo)記過(guò)程與標(biāo)記清除算法一樣),讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。這樣可以解決內(nèi)存碎片問(wèn)題。

4. 分代收集算法

就是針對(duì)Java堆內(nèi)存中新生代、老年代等采用不同的垃圾回收算法。如在新生代中,往往只有少量對(duì)象存活(最后會(huì)進(jìn)入老年代),則適合用復(fù)制算法。而老年代中對(duì)象存活率較高,沒(méi)有額外的空間對(duì)它進(jìn)行分配擔(dān)保,就使用標(biāo)記清除算法。

當(dāng)然實(shí)際應(yīng)用中,使用什么算法,要看使用的垃圾回收器。

本文來(lái)自博客園,原文鏈接:https://www.cnblogs.com/bigdatalearnshare/p/13864712.html

申請(qǐng)創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!

相關(guān)標(biāo)簽
JVM內(nèi)存管理

相關(guān)文章

熱門(mén)排行

信息推薦