溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中怎么使用本機(jī)內(nèi)存

發(fā)布時(shí)間:2021-07-02 14:27:02 來(lái)源:億速云 閱讀:284 作者:Leah 欄目:編程語(yǔ)言

這篇文章將為大家詳細(xì)講解有關(guān)Java中怎么使用本機(jī)內(nèi)存,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

Java 堆和垃圾收集

Java 堆是分配了對(duì)象的內(nèi)存區(qū)域。大多數(shù) Java SE 實(shí)現(xiàn)都擁有一個(gè)邏輯堆,但是一些專家級(jí) Java 運(yùn)行時(shí)擁有多個(gè)堆,比如實(shí)現(xiàn) Java 實(shí)時(shí)規(guī)范(Real Time Specification for Java,RTSJ)的運(yùn)行時(shí)。一個(gè)物理堆可被劃分為多個(gè)邏輯扇區(qū),具體取決于用于管理堆內(nèi)存的垃圾收集(GC)算法。這些扇區(qū)通常實(shí)現(xiàn)為連續(xù)的本機(jī)內(nèi)存塊,這些內(nèi)存塊受 Java 內(nèi)存管理器(包含垃圾收集器)控制。

堆的大小可以在 Java 命令行使用 -Xmx-Xms 選項(xiàng)來(lái)控制(mx 表示堆的***大小,ms 表示初始大?。1M管邏輯堆(經(jīng)常被使用的內(nèi)存區(qū)域)可以根據(jù)堆上的對(duì)象數(shù)量和在 GC 上花費(fèi)的時(shí)間而增大和縮小,但使用的本機(jī)內(nèi)存大小保持不變,而且由 -Xmx 值(***堆大小)指定。大部分 GC 算法依賴于被分配為連續(xù)的內(nèi)存塊的堆,因此不能在堆需要擴(kuò)大時(shí)分配更多本機(jī)內(nèi)存。所有堆內(nèi)存必須預(yù)先保留。

保留本機(jī)內(nèi)存與分配本機(jī)內(nèi)存不同。當(dāng)本機(jī)內(nèi)存被保留時(shí),無(wú)法使用物理內(nèi)存或其他存儲(chǔ)器作為備用內(nèi)存。盡管保留地址空間塊不會(huì)耗盡物理資源,但會(huì)阻止內(nèi)存被用于其他用途。由保留從未使用的內(nèi)存導(dǎo)致的泄漏與泄漏分配的內(nèi)存一樣嚴(yán)重。

當(dāng)使用的堆區(qū)域縮小時(shí),一些垃圾收集器會(huì)回收堆的一部分(釋放堆的后備存儲(chǔ)空間),從而減少使用的物理內(nèi)存。

對(duì)于維護(hù) Java 堆的內(nèi)存管理系統(tǒng),需要更多本機(jī)內(nèi)存來(lái)維護(hù)它的狀態(tài)。當(dāng)進(jìn)行垃圾收集時(shí),必須分配數(shù)據(jù)結(jié)構(gòu)來(lái)跟蹤空閑存儲(chǔ)空間和記錄進(jìn)度。這些數(shù)據(jù)結(jié)構(gòu)的確切大小和性質(zhì)因?qū)崿F(xiàn)的不同而不同,但許多數(shù)據(jù)結(jié)構(gòu)都與堆大小成正比。

即時(shí) (JIT) 編譯器

JIT 編譯器在運(yùn)行時(shí)編譯 Java 字節(jié)碼來(lái)優(yōu)化本機(jī)可執(zhí)行代碼。這極大地提高了 Java 運(yùn)行時(shí)的速度,并且支持 Java 應(yīng)用程序以與本機(jī)代碼相當(dāng)?shù)乃俣冗\(yùn)行。

字節(jié)碼編譯使用本機(jī)內(nèi)存(使用方式與 gcc 等靜態(tài)編譯器使用內(nèi)存來(lái)運(yùn)行一樣),但 JIT 編譯器的輸入(字節(jié)碼)和輸出(可執(zhí)行代碼)必須也存儲(chǔ)在本機(jī)內(nèi)存中。包含多個(gè)經(jīng)過(guò) JIT 編譯的方法的 Java 應(yīng)用程序會(huì)使用比小型應(yīng)用程序更多的本機(jī)內(nèi)存。

類和類加載器

Java 應(yīng)用程序由一些類組成,這些類定義對(duì)象結(jié)構(gòu)和方法邏輯。Java 應(yīng)用程序也使用 Java 運(yùn)行時(shí)類庫(kù)(比如 java.lang.String)中的類,也可以使用第三方庫(kù)。這些類需要存儲(chǔ)在內(nèi)存中以備使用。

存儲(chǔ)類的方式取決于具體實(shí)現(xiàn)。Sun JDK 使用***生成(permanent generation,PermGen)堆區(qū)域。Java 5 的 IBM 實(shí)現(xiàn)會(huì)為每個(gè)類加載器分配本機(jī)內(nèi)存塊,并將類數(shù)據(jù)存儲(chǔ)在其中?,F(xiàn)代 Java 運(yùn)行時(shí)擁有類共享等技術(shù),這些技術(shù)可能需要將共享內(nèi)存區(qū)域映射到地址空間。要理解這些分配機(jī)制如何影響您 Java 運(yùn)行時(shí)的本機(jī)內(nèi)存占用,您需要查閱該實(shí)現(xiàn)的技術(shù)文檔。然而,一些普遍的事實(shí)會(huì)影響所有實(shí)現(xiàn)。

從最基本的層面來(lái)看,使用更多的類將需要使用更多內(nèi)存。(這可能意味著您的本機(jī)內(nèi)存使用量會(huì)增加,或者您必須明確地重新設(shè)置 PermGen 或共享類緩存等區(qū)域的大小,以裝入所有類)。記住,不僅您的應(yīng)用程序需要加載到內(nèi)存中,框架、應(yīng)用服務(wù)器、第三方庫(kù)以及包含類的 Java 運(yùn)行時(shí)也會(huì)按需加載并占用空間。

Java 運(yùn)行時(shí)可以卸載類來(lái)回收空間,但是只有在非常嚴(yán)酷的條件下才會(huì)這樣做。不能卸載單個(gè)類,而是卸載類加載器,隨其加載的所有類都會(huì)被卸載。只有在以下情況下才能卸載類加載器:

  • Java 堆不包含對(duì)表示該類加載器的 java.lang.ClassLoader 對(duì)象的引用。

  • Java 堆不包含對(duì)表示類加載器加載的類的任何 java.lang.Class 對(duì)象的引用。

  • 在 Java 堆上,該類加載器加載的任何類的所有對(duì)象都不再存活(被引用)。

需要注意的是,Java 運(yùn)行時(shí)為所有 Java 應(yīng)用程序創(chuàng)建的 3 個(gè)默認(rèn)類加載器( bootstrap、extensionapplication )都不可能滿足這些條件,因此,任何系統(tǒng)類(比如 java.lang.String)或通過(guò)應(yīng)用程序類加載器加載的任何應(yīng)用程序類都不能在運(yùn)行時(shí)釋放。

即使類加載器適合進(jìn)行收集,運(yùn)行時(shí)也只會(huì)將收集類加載器作為 GC 周期的一部分。一些實(shí)現(xiàn)只會(huì)在某些 GC 周期中卸載類加載器。

也可能在運(yùn)行時(shí)生成類,而不用釋放它。許多 JEE 應(yīng)用程序使用 JavaServer Pages (JSP) 技術(shù)來(lái)生成 Web 頁(yè)面。使用 JSP 會(huì)為執(zhí)行的每個(gè) .jsp 頁(yè)面生成一個(gè)類,并且這些類會(huì)在加載它們的類加載器的整個(gè)生存期中一直存在 —— 這個(gè)生存期通常是 Web 應(yīng)用程序的生存期。

另一種生成類的常見(jiàn)方法是使用 Java 反射。反射的工作方式因 Java 實(shí)現(xiàn)的不同而不同,但 Sun 和 IBM 實(shí)現(xiàn)都使用了這種方法,我馬上就會(huì)講到。

當(dāng)使用 java.lang.reflect API 時(shí),Java 運(yùn)行時(shí)必須將一個(gè)反射對(duì)象(比如 java.lang.reflect.Field)的方法連接到被反射到的對(duì)象或類。這可以通過(guò)使用 Java 本機(jī)接口(Java Native Interface,JNI)訪問(wèn)器來(lái)完成,這種方法需要的設(shè)置很少,但是速度緩慢。也可以在運(yùn)行時(shí)為您想要反射到的每種對(duì)象類型動(dòng)態(tài)構(gòu)建一個(gè)類。后一種方法在設(shè)置上更慢,但運(yùn)行速度更快,非常適合于經(jīng)常反射到一個(gè)特定類的應(yīng)用程序。

Java 運(yùn)行時(shí)在最初幾次反射到一個(gè)類時(shí)使用 JNI 方法,但當(dāng)使用了若干次 JNI 方法之后,訪問(wèn)器會(huì)膨脹為字節(jié)碼訪問(wèn)器,這涉及到構(gòu)建類并通過(guò)新的類加載器進(jìn)行加載。執(zhí)行多次反射可能導(dǎo)致創(chuàng)建了許多訪問(wèn)器類和類加載器。保持對(duì)反射對(duì)象的引用會(huì)導(dǎo)致這些類一直存活,并繼續(xù)占用空間。因?yàn)閯?chuàng)建字節(jié)碼訪問(wèn)器非常緩慢,所以 Java 運(yùn)行時(shí)可以緩存這些訪問(wèn)器以備以后使用。一些應(yīng)用程序和框架還會(huì)緩存反射對(duì)象,這進(jìn)一步增加了它們的本機(jī)內(nèi)存占用。

JNI

JNI 支持本機(jī)代碼(使用 C 和 C++ 等本機(jī)編譯語(yǔ)言編寫的應(yīng)用程序)調(diào)用 Java 方法,反之亦然。Java 運(yùn)行時(shí)本身極大地依賴于 JNI 代碼來(lái)實(shí)現(xiàn)類庫(kù)功能,比如文件和網(wǎng)絡(luò) I/O。JNI 應(yīng)用程序可能通過(guò) 3 種方式增加 Java 運(yùn)行時(shí)的本機(jī)內(nèi)存占用:

  • JNI 應(yīng)用程序的本機(jī)代碼被編譯到共享庫(kù)中,或編譯為加載到進(jìn)程地址空間中的可執(zhí)行文件。大型本機(jī)應(yīng)用程序可能僅僅加載就會(huì)占用大量進(jìn)程地址空間。

  • 本機(jī)代碼必須與 Java 運(yùn)行時(shí)共享地址空間。任何本機(jī)代碼分配或本機(jī)代碼執(zhí)行的內(nèi)存映射都會(huì)耗用 Java 運(yùn)行時(shí)的內(nèi)存。

  • 某些 JNI 函數(shù)可能在它們的常規(guī)操作中使用本機(jī)內(nèi)存。GetTypeArrayElementsGetTypeArrayRegion 函數(shù)可以將 Java 堆數(shù)據(jù)復(fù)制到本機(jī)內(nèi)存緩沖區(qū)中,以供本機(jī)代碼使用。是否復(fù)制數(shù)據(jù)依賴于運(yùn)行時(shí)實(shí)現(xiàn)。(IBM Developer Kit for Java 5.0 和更高版本會(huì)進(jìn)行本機(jī)復(fù)制)。通過(guò)這種方式訪問(wèn)大量 Java 堆數(shù)據(jù)可能會(huì)使用大量本機(jī)堆。

NIO

Java 1.4 中添加的新 I/O (NIO) 類引入了一種基于通道和緩沖區(qū)來(lái)執(zhí)行 I/O 的新方式。就像 Java 堆上的內(nèi)存支持 I/O 緩沖區(qū)一樣,NIO 添加了對(duì)直接 ByteBuffer 的支持(使用 java.nio.ByteBuffer.allocateDirect() 方法進(jìn)行分配), ByteBuffer 受本機(jī)內(nèi)存而不是 Java 堆支持。直接 ByteBuffer 可以直接傳遞到本機(jī)操作系統(tǒng)庫(kù)函數(shù),以執(zhí)行 I/O — 這使這些函數(shù)在一些場(chǎng)景中要快得多,因?yàn)樗鼈兛梢员苊庠?Java 堆與本機(jī)堆之間復(fù)制數(shù)據(jù)。

對(duì)于在何處存儲(chǔ)直接 ByteBuffer 數(shù)據(jù),很容易產(chǎn)生混淆。應(yīng)用程序仍然在 Java 堆上使用一個(gè)對(duì)象來(lái)編排 I/O 操作,但持有該數(shù)據(jù)的緩沖區(qū)將保存在本機(jī)內(nèi)存中,Java 堆對(duì)象僅包含對(duì)本機(jī)堆緩沖區(qū)的引用。非直接 ByteBuffer 將其數(shù)據(jù)保存在 Java 堆上的 byte[] 數(shù)組中。下圖展示了直接與非直接 ByteBuffer 對(duì)象之間的區(qū)別:

直接與非直接 java.nio.ByteBuffer 的內(nèi)存拓?fù)浣Y(jié)構(gòu)
Java中怎么使用本機(jī)內(nèi)存

直接 ByteBuffer 對(duì)象會(huì)自動(dòng)清理本機(jī)緩沖區(qū),但這個(gè)過(guò)程只能作為 Java 堆 GC 的一部分來(lái)執(zhí)行,因此它們不會(huì)自動(dòng)響應(yīng)施加在本機(jī)堆上的壓力。GC 僅在 Java 堆被填滿,以至于無(wú)法為堆分配請(qǐng)求提供服務(wù)時(shí)發(fā)生,或者在 Java 應(yīng)用程序中顯式請(qǐng)求它發(fā)生(不建議采用這種方式,因?yàn)檫@可能導(dǎo)致性能問(wèn)題)。

發(fā)生垃圾收集的情形可能是,本機(jī)堆被填滿,并且一個(gè)或多個(gè)直接 ByteBuffers 適合于垃圾收集(并且可以被釋放來(lái)騰出本機(jī)堆的空間),但 Java 堆幾乎總是空的,所以不會(huì)發(fā)生垃圾收集。

線程

應(yīng)用程序中的每個(gè)線程都需要內(nèi)存來(lái)存儲(chǔ)器堆棧(用于在調(diào)用函數(shù)時(shí)持有局部變量并維護(hù)狀態(tài)的內(nèi)存區(qū)域)。每個(gè) Java 線程都需要堆??臻g來(lái)運(yùn)行。根據(jù)實(shí)現(xiàn)的不同,Java 線程可以分為本機(jī)線程和 Java 堆棧。除了堆??臻g,每個(gè)線程還需要為線程本地存儲(chǔ)(thread-local storage)和內(nèi)部數(shù)據(jù)結(jié)構(gòu)提供一些本機(jī)內(nèi)存。

堆棧大小因 Java 實(shí)現(xiàn)和架構(gòu)的不同而不同。一些實(shí)現(xiàn)支持為 Java 線程指定堆棧大小,其范圍通常在 256KB 到 756KB 之間。

盡管每個(gè)線程使用的內(nèi)存量非常小,但對(duì)于擁有數(shù)百個(gè)線程的應(yīng)用程序來(lái)說(shuō),線程堆棧的總內(nèi)存使用量可能非常大。如果運(yùn)行的應(yīng)用程序的線程數(shù)量比可用于處理它們的處理器數(shù)量多,效率通常很低,并且可能導(dǎo)致糟糕的性能和更高的內(nèi)存占用。

關(guān)于Java中怎么使用本機(jī)內(nèi)存就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI