Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

前言

創(chuàng)新互聯(lián)建站專注于丹東企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站建設(shè),電子商務(wù)商城網(wǎng)站建設(shè)。丹東網(wǎng)站建設(shè)公司,為丹東等地區(qū)提供建站服務(wù)。全流程按需定制,專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)建站專業(yè)和態(tài)度為您提供的服務(wù)

深入研究Java內(nèi)存管理,將增強(qiáng)你對堆如何工作、引用類型和垃圾回收的認(rèn)識。

你可能會思考,如果你使用Java編程,關(guān)于內(nèi)存如何工作你需要了解哪些哪些信息?Java可以進(jìn)行自動內(nèi)存管理,而且有一個(gè)很好的、安靜的垃圾回收器,它在后臺工作,清理那些未使用的對象并釋放一些內(nèi)存。

Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

因此,作為一名Java程序員,你不需要再為銷毀無用對象這樣的問題而煩惱了。但是,雖然這個(gè)過程在Java中是自動的,它也不能保證任何事情。由于不知道垃圾回收器和Java內(nèi)存是如何設(shè)計(jì)的,有些對象即使你不再使用了,卻也不符合垃圾回收的條件。

因此,了解Java中內(nèi)存實(shí)際是如何工作的非常重要,因?yàn)樗鼮槟憔帉懜咝阅芎蛢?yōu)化的應(yīng)用程序提供了幫助,這些應(yīng)用程序永遠(yuǎn)不會因內(nèi)存不足而崩潰。另一方面,當(dāng)你發(fā)現(xiàn)自己處于糟糕的境地時(shí),你將能夠很快發(fā)現(xiàn)內(nèi)存的漏洞。

首先,讓我們看看內(nèi)存在Java中通常是如何組織的:

Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

通常,內(nèi)存分為兩大部分:堆棧和堆。請記住,內(nèi)存類型在上圖中的大小與實(shí)際內(nèi)存大小不成比例。與堆棧相比,堆是一個(gè)巨大數(shù)量的內(nèi)存。

堆棧

堆棧內(nèi)存負(fù)責(zé)保存對堆對象的引用和存儲值類型(在Java中也稱為基元類型),值類型保存值本身而不保存對堆中對象的引用。

此外,堆棧上的變量具有一定的可見性,也稱為作用域。只有活躍作用域內(nèi)的對象才能被使用。例如,假設(shè)我們沒有任何全局作用域變量(字段),只有局部變量,如果編譯器執(zhí)行方法的主體,它只能訪問方法主體內(nèi)堆棧中的對象。它不能訪問其它局部變量,因?yàn)檫@些變量超出了作用域。一旦方法完成并返回,堆棧頂部就會溢出,活躍作用域也會發(fā)生變化。

或許你注意到了在上圖中顯示的多個(gè)堆棧內(nèi)存,這是因?yàn)镴ava中的堆棧內(nèi)存是按線程分配的。因此,每次一個(gè)線程被創(chuàng)建和啟動時(shí),它都有自己的堆棧內(nèi)存,并且不能訪問另一個(gè)線程的堆棧內(nèi)存。

堆內(nèi)存將實(shí)際對象存儲在內(nèi)存中。這些對象被堆棧中的變量引用。例如,讓我們分析下面一行代碼發(fā)生了什么:

StringBuilder?builder?=?new?StringBuilder();

“new”關(guān)鍵字負(fù)責(zé)確保堆上有足夠的可用空間,在內(nèi)存中創(chuàng)建一個(gè)StringBuilder類型的對象,并通過堆棧中的“builder”引用它。

每個(gè)正在運(yùn)行的JVM進(jìn)程只有一個(gè)堆內(nèi)存。因此,無論運(yùn)行多少線程,這都是內(nèi)存中的一個(gè)共享部分。實(shí)際上,堆結(jié)構(gòu)與上圖中顯示的略有不同。堆本身被分成幾個(gè)部分,這有助于垃圾回收進(jìn)程。

最大堆棧和堆大小都沒有預(yù)定義 - 這取決于正在運(yùn)行的計(jì)算機(jī)。 然而,在后文中,我們將研究一些JVM配置,這些配置允許我們?yōu)檎谶\(yùn)行的應(yīng)用程序明確設(shè)定它們的大小

Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

引用類型

如果仔細(xì)觀察內(nèi)存結(jié)構(gòu)圖片,你或許會注意到,代表對堆中對象引用的箭頭的樣式實(shí)際是不同的。這是因?yàn)?,在Java編程語言中,我們有不同類型的引用:強(qiáng)引用、弱引用、軟引用和虛引用。引用類型之間的區(qū)別在于它們所引用堆上的對象在不同的條件下可以被作為垃圾回收。讓我們來仔細(xì)認(rèn)識一下每一種引用類型。

1. 強(qiáng)引用>>>

這種引用類型是我們都習(xí)慣并且最受歡迎的引用類型。在上面的StringBuilder示例中,我們實(shí)際上使用了對堆中對象的強(qiáng)引用。當(dāng)有一個(gè)強(qiáng)引用指向堆上的對象時(shí),或者通過一系列強(qiáng)引用可以強(qiáng)訪問該對象,則該對象不會被作為垃圾回收。

2. 弱引用>>

簡單來說,在下一個(gè)垃圾回收進(jìn)程之后,對堆中對象的弱引用很可能不會繼續(xù)存在了。弱引用的創(chuàng)建示例如下:

WeakReference<StringBuilder>?reference?=?new?WeakReference<>(new?StringBuilder());

弱引用的一個(gè)很好的用例是緩存方案。假設(shè)你檢索了一些數(shù)據(jù),并且還希望將其存儲在內(nèi)存中—這樣同樣的數(shù)據(jù)可以被再次請求。另一方面,你不確定何時(shí)或者是否會再次請求這些數(shù)據(jù)。因此,你可以保留對它的弱引用,萬一垃圾回收器運(yùn)行,它可能會破壞堆中的對象。因此,過了一會兒,如果你想要檢索你引用的對象,你可能會突然得到一個(gè)空的返回值。緩存方案的一個(gè)很好的使用是回收WeakHashMap。如果我們在Java API中打開WeakHashMap類,我們會看到它的條目實(shí)際上擴(kuò)展了WeakReference類,并使用它的引用字段作為映射的關(guān)鍵字:

private?static?class?Entry<K,V>?extends?WeakReference<Object>?implements?Map.Entry<K,V>?
{?
V?value;
......
}

一旦WeakHashMap中的一個(gè)關(guān)鍵字被進(jìn)行了垃圾回收,整個(gè)條目就會從映射中移除。

3. 軟引用>>>

這種引用類型用于對內(nèi)存更敏感的方案,因?yàn)橹挥挟?dāng)應(yīng)用程序內(nèi)存不足時(shí),所引用的對象才會被作為垃圾回收。因此,只要沒有迫切需要釋放出一些內(nèi)存空間,垃圾回收器就不會去回收軟引用的對象。Java保證在拋出OutOfMemoryError之前清除所有軟引用的對象。Javadocs表明:“在虛擬機(jī)拋出OutOfMemoryError之前,所有對可軟訪問對象的軟引用都會確保被清除?!?與弱引用類似,軟引用的創(chuàng)建示例如下:

SoftReference<StringBuilder>?reference?=?new?SoftReference<>(new?StringBuilder());
......
}

4. 虛引用>>>

用于算法檢查后的清理操作,因?yàn)槲覀冎烙行ο蟛恍枰俅嬖?。僅與引用隊(duì)列一起使用,因?yàn)榇祟愐玫?get()方法將始終返回空值。這些引用類型被認(rèn)為是優(yōu)于終結(jié)器的。

如何引用字符串

Java中對字符串類型的處理略有不同。字符串是不可變的,這意味著每次使用字符串執(zhí)行操作時(shí),實(shí)際上都會在堆上創(chuàng)建另一個(gè)對象。對于字符串,Java在內(nèi)存中進(jìn)行字符串池管理。這意味著Java會盡可能地存儲和重用字符串。對于字符串文字,更是這樣。例如:

String?localPrefix?=?"297";?//1
String?prefix?=?"297";??????//2
if?(prefix?==?localPrefix)
{
????System.out.println("Strings?are?equal"?);
}
else
{
????System.out.println("Strings?are?different");
}

運(yùn)行時(shí),將輸出以下內(nèi)容:

Strings are equal

因此,可以看出在比較了字符串類型的兩個(gè)引用之后,它們實(shí)際上指向了堆中的相同對象。但是,這對于被計(jì)算的字符串無效。假設(shè)我們對上述代碼的//1行進(jìn)行以下更改

String?localPrefix?=?new?Integer(297).toString();?//1

輸出:

Strings are different

在這種情況下,我們實(shí)際上看到堆上有兩個(gè)不同的對象。如果我們考慮到計(jì)算出的字符串會被經(jīng)常使用,我們可以強(qiáng)制JVM通過在計(jì)算的字符串末尾添加.intern()方法將計(jì)算的字符串添加到字符串池當(dāng)中:

String?localPrefix?=?new?Integer(297).toString().intern();?//1

進(jìn)行上述更改后輸出如下:

Strings are equal

垃圾回收進(jìn)程

正如前面所討論的,根據(jù)堆棧中的變量對堆中對象的引用類型,在某個(gè)確定的時(shí)間點(diǎn),該對象符合垃圾回收器的條件。

Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

比方說,所有紅色的對象都符合被垃圾回收器的條件。 你可能會注意到堆上有一個(gè)對象,它對同一堆上的其它對象進(jìn)行了強(qiáng)引用(例如,可能是引用了自己項(xiàng)的列表,或者是具有兩個(gè)引用類型字段的對象)。但是,由于堆棧中的引用丟失,這個(gè)對象就無法再被訪問,因此它也成了垃圾。

為了更深入地了解細(xì)節(jié),我們先提出以下幾點(diǎn):

1.這個(gè)過程是由Java自動觸發(fā)的,何時(shí)啟動以及是否啟動此過程取決于Java。

2.實(shí)際上這個(gè)進(jìn)程是昂貴的。當(dāng)垃圾回收器運(yùn)行時(shí),應(yīng)用程序中的所有線程都會暫停(取決于GC類型,稍后將對此進(jìn)行討論)。

3.這實(shí)際上是一個(gè)比垃圾回收和釋放內(nèi)存更復(fù)雜的進(jìn)程。

盡管由Java決定何時(shí)運(yùn)行垃圾回收器,你也可以直接調(diào)用System.gc( )并期望垃圾回收器在執(zhí)行這行代碼時(shí)運(yùn)行,對吧?

這是一個(gè)錯誤的假設(shè)。

你只需要讓Java運(yùn)行垃圾回收器,但是是否運(yùn)行垃圾回收器仍然取決于Java。無論如何,不建議直接調(diào)用System.gc( )。

由于這是一個(gè)非常復(fù)雜的過程,并且它可能會影響你程序的表現(xiàn),它需要以一個(gè)智能的方式實(shí)現(xiàn)。 一個(gè)被稱作“標(biāo)記和掃描”的進(jìn)程來完成此任務(wù)。Java分析堆棧中的變量并“標(biāo)記”所有保持活躍的對象,然后清除所有不會使用的對象。

實(shí)際上,Java并沒有回收任何垃圾。事實(shí)上,垃圾越多,標(biāo)記為活躍的對象就越少,進(jìn)程也就越快。為了使這個(gè)進(jìn)程更加優(yōu)化,堆內(nèi)存實(shí)際由多個(gè)部分組成。我們可以通過JVisualVM(Java JDK附帶的工具)可視化內(nèi)存使用情況和其它一些有用的東西。您唯一需要做的就是安裝一個(gè)名為Visual GC的插件,它允許您查看內(nèi)存的實(shí)際結(jié)構(gòu)。讓我們放大一點(diǎn),分解大局:

Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

當(dāng)一個(gè)對象被創(chuàng)建時(shí),它被分配到Eden(1)區(qū)。因?yàn)镋den區(qū)的空間沒有那么大,它很快就滿了。垃圾回收器在Eden區(qū)運(yùn)行,并標(biāo)記出活躍的對象。

一旦一個(gè)對象在一次垃圾回收進(jìn)程中存活,它就會被移動到所謂的幸存者區(qū)S0(2)中。 垃圾器第回收二次在Eden區(qū)上運(yùn)行時(shí),它會將所有幸存的對象移動到S1(3)區(qū)中。此外,當(dāng)前在S0(2)區(qū)上的所有內(nèi)容都將被移動到S1(3)區(qū)中。

如果一個(gè)對象在X輪垃圾回收中存活了下來(取決于JVM的實(shí)現(xiàn),在我的例子中是8輪),那么它很可能會永遠(yuǎn)存活下來,并被移入到Old(4)區(qū)。

結(jié)合目前為止所說的一切,如果你看一下圖中標(biāo)號(6)的垃圾回收器,它每次運(yùn)行時(shí),你都可以看到對象切換到幸存者空間,并且Eden區(qū)的空間增大了。如此反復(fù)。老一代也可以被作為垃圾回收,但由于它在內(nèi)存中空間是比Eden區(qū)更大的部分,因此這種情況不會經(jīng)常發(fā)生。Metaspace(5)用于在JVM中存儲已加載類的元數(shù)據(jù)。

所呈現(xiàn)的圖片實(shí)際上是一個(gè)Java 8的應(yīng)用程序。在Java 8之前的版本,內(nèi)存的結(jié)構(gòu)有點(diǎn)不同。元空間實(shí)際上稱為PermGen. 區(qū)。例如,在Java 6中,此空間還為字符串池存儲了內(nèi)存。因此,如果Java 6應(yīng)用程序中有太多字符串,則它可能會崩潰。歡迎大家關(guān)注我的公種浩【程序員追風(fēng)】,文章都會在里面更新,整理的資料也會放在里面。

Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?

垃圾回收器類型

實(shí)際上,JVM有三種類型的垃圾回收器,程序員可以選擇應(yīng)該使用哪種垃圾回收器。默認(rèn)情況下,Java根據(jù)底層硬件選擇要使用的垃圾回收器類型。

1.串行垃圾回收器 - 一個(gè)單線程回收器。 主要適用于數(shù)據(jù)使用量較小的小型應(yīng)用程序。 可以通過指定命令行選項(xiàng)來啟用:-XX:+ UseSerialGC

2.并行垃圾回收器- 從命名可以看出,串行垃圾回收器和并行垃圾回收器之間的區(qū)別在于并行垃圾回收器使用多個(gè)線程來執(zhí)行垃圾回收進(jìn)行。并行垃圾回收器也被稱作吞吐量回收器。可以通過直接指定選項(xiàng)來啟用它:-XX:+ UseParallelGC

3.主要并發(fā)標(biāo)記垃圾回收器 - 如果你還記得,在本文前面提到垃圾回收過程實(shí)際上相當(dāng)昂貴,并且當(dāng)它運(yùn)行時(shí),所有線程都被暫停。但是,我們有這種大多數(shù)并發(fā)GC類型,它聲明它與應(yīng)用程序并發(fā)工作。但是,它有“大多數(shù)”并發(fā)的原因。它不能100%同時(shí)應(yīng)用于應(yīng)用程序。線程暫停一段時(shí)間。盡管如此,暫停時(shí)間盡可能短,以實(shí)現(xiàn)最佳的GC性能。實(shí)際上,有兩種類型的大多數(shù)并發(fā)GC:

3.1垃圾優(yōu)先 - 應(yīng)用程序合理暫停時(shí)間內(nèi)的高吞吐量。 通過以下選項(xiàng)啟用:-XX:+ UseG1GC

3.2并發(fā)標(biāo)記掃描 - 應(yīng)用程序暫停時(shí)間保持最短。可以通過指定選項(xiàng)來啟用:-XX:+ UseConcMarkSweepGC。從JDK 9開始,這個(gè)垃圾回收器類型不推薦使用。。

提示和技巧

1.為了最小化內(nèi)存的占用,請盡可能限制變量的作用域。請記住,每次堆棧中的頂級作用域溢出時(shí),來自該作用域的引用都會丟失,這可能會導(dǎo)致相應(yīng)的對象被作為垃圾回收。

2.直接對空的、廢棄對象的引用,這會導(dǎo)致被引用的對象被作為垃圾回收。

3.避免成為終結(jié)者。 它們放慢了進(jìn)程,不保證任何事情, 更喜歡進(jìn)行對虛引用的清理工作。

4.當(dāng)弱引用或軟引用適用時(shí),請不要使用強(qiáng)引用。最常見的內(nèi)存缺陷是緩存方案,即使數(shù)據(jù)可能不需要,也會被保存在內(nèi)存中。

5.JVisualVM還具有在某一點(diǎn)時(shí)間點(diǎn)進(jìn)行堆轉(zhuǎn)儲的功能,因此你可以分析每一類所占用的內(nèi)存量。

6.根據(jù)你的應(yīng)用程序需求來配置JVM。運(yùn)行應(yīng)用程序時(shí),明確指定JVM的堆大小。內(nèi)存分配進(jìn)程是寶貴的,因此要為堆分配一個(gè)合理的初始最大內(nèi)存空間。如果你知道一開始使用較小的初始堆空間是沒有意義的,JVM將擴(kuò)展這個(gè)內(nèi)存空間。 根據(jù)以下命令來明確內(nèi)存空間:

(1)初始堆大小 -Xms512m 將初始堆大小設(shè)置為512 mb。

(2)最大堆大小 -Xmx1024m 將最大堆大小設(shè)置為1024 mb。

(3)線程堆棧大小 -Xss128m 將線程堆棧大小設(shè)置為128mb。

(4)新生代堆大小 -Xmn256m 將新生代堆大小設(shè)置為256mb。

7.如果Java應(yīng)用程序崩潰并出現(xiàn)OutOfMemoryError,你需要一些額外的信息來檢測漏洞,運(yùn)行以下進(jìn)程:-XX:HeapDumpOnOutOfMemory,它將在下次發(fā)生此錯誤時(shí)創(chuàng)建堆轉(zhuǎn)儲文件。

8.使用-verbose:gc選項(xiàng)獲取垃圾回收輸出。 每次進(jìn)行垃圾回收時(shí),都會生成一個(gè)輸出

總結(jié)

從內(nèi)存資源的角度看,了解內(nèi)存是如何組織的,會為你編寫良好、優(yōu)化的代碼提供優(yōu)勢。這樣做的好處是,你可以通過提供最適合你所運(yùn)行應(yīng)用程序的不同配置,來優(yōu)化你正在運(yùn)行的JVM。如果使用正確的工具,發(fā)現(xiàn)和修復(fù)內(nèi)存漏洞只是一件容易的事情。

最后

歡迎大家一起交流,喜歡文章記得點(diǎn)個(gè)贊喲,感謝支持!

標(biāo)題名稱:Java內(nèi)存大家都知道,但你知道要怎么管理Java內(nèi)存嗎?
網(wǎng)站鏈接:http://muchs.cn/article44/jopihe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、網(wǎng)站導(dǎo)航、標(biāo)簽優(yōu)化網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)