解讀Jvm的內(nèi)存結(jié)構(gòu)與GC及jvm參數(shù)調(diào)優(yōu)
一、JVM 內(nèi)存結(jié)構(gòu)
1、對(duì)象主要存放在堆內(nèi)存中;方法和屬性主要存放在棧內(nèi)存中。
2、棧是運(yùn)行時(shí)單位,用來解決程序運(yùn)行時(shí)的問題,堆是存儲(chǔ)單位,解決數(shù)據(jù)存儲(chǔ)的問題。
3、堆伴隨著JVM的啟動(dòng)而創(chuàng)建。
結(jié)構(gòu)圖
1、類加載子系統(tǒng)
負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)加載Class信息,加載的信息存放在一塊稱之方法區(qū)的內(nèi)存空間。
2、方法區(qū)(method)
存放類、常量、常量池信息、包括字符串字面量和數(shù)字常量等, 只要是 static 關(guān)鍵字修飾的都在方法區(qū)。
方法區(qū)的數(shù)據(jù)是所有線程共享的。
3、堆(heap)
幾乎所有的對(duì)象實(shí)例都存放到Java堆中,堆空間是所有線程共享,比如new 出來的對(duì)象。
在堆中的分配的內(nèi)存,由java虛擬機(jī)自動(dòng)垃圾回收器來管理。
其中堆又分為了新生代和老年代和永久代 (jdk 1.8后取消永久代增加元空間)。
優(yōu)點(diǎn):是可以動(dòng)態(tài)地分配內(nèi)存大小,垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)。
缺點(diǎn):由于要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,存取速度較慢
4、棧(stack)
在多線程環(huán)境下,每個(gè)線程擁有一個(gè)棧和一個(gè)程序計(jì)數(shù)器,是線程私有的資源。
方法和屬性主要存放在棧內(nèi)存中。
java中的所有引用都在棧中,并指向堆。
引用是創(chuàng)建對(duì)象的時(shí)候創(chuàng)建的(new ),第二次創(chuàng)建發(fā)現(xiàn)棧中如果有該對(duì)象的引用就使用原引用,不會(huì)新建
優(yōu)點(diǎn):存取速度比堆要快,僅次于直接位于CPU中的寄存器,
缺點(diǎn):存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性,生存期就是他與對(duì)象的關(guān)聯(lián)的時(shí)間
5、本地方法棧
用于本地方法調(diào)用。Java虛擬機(jī)允許Java直接調(diào)用本地方法(通過使用C語言寫)
6、pc寄存器(了解即可)
PC(Program Couneter)寄存器也是每個(gè)線程私有的空間,
Java虛擬機(jī)會(huì)為每個(gè)線程創(chuàng)建PC寄存器,在任意時(shí)刻,
一個(gè)Java線程總是在執(zhí)行一個(gè)方法,這個(gè)方法稱為當(dāng)前方法,
如果當(dāng)前方法不是本地方法,PC寄存器總會(huì)執(zhí)行當(dāng)前正在被執(zhí)行的指令,
如果是本地方法,則PC寄存器值為Underfined,
寄存器存放如果當(dāng)前執(zhí)行環(huán)境指針、程序技術(shù)器、操作棧指針、計(jì)算的變量指針等信息。
7、執(zhí)行引擎
虛擬機(jī)核心的組件就是執(zhí)行引擎,它負(fù)責(zé)執(zhí)行虛擬機(jī)的字節(jié)碼,一般戶先進(jìn)行編譯成機(jī)器碼后執(zhí)行。
8、垃圾收集器
垃圾收集系統(tǒng)是Java的核心,也是不可少的,Java有一套自己進(jìn)行垃圾清理的機(jī)制,開發(fā)人員無需手工清理
二、堆–> 新生代/新生代/永久代
1、新生代
主要是用來存放新生的對(duì)象。一般占據(jù)堆的1/3空間。
由于頻繁創(chuàng)建對(duì)象,所以新生代會(huì)頻繁觸發(fā)MinorGC進(jìn)行垃圾回收。
新生代又分為 Eden區(qū)、ServivorFrom、ServivorTo三個(gè)區(qū)。
Eden :Java新對(duì)象的出生地(如果新創(chuàng)建的對(duì)象占用內(nèi)存很大,則直接分配到老年代)。當(dāng)Eden區(qū)內(nèi)存不夠的時(shí)候就會(huì)觸發(fā)MinorGC,對(duì)新生代區(qū)進(jìn)行一次垃圾回收
ServivorTo :保留了一次MinorGC過程中的幸存者。
ServivorFrom :上一次GC的幸存者,作為這一次GC的被掃描者。
MinorGC 是采用復(fù)制算法來清理垃圾
2、老年代
主要是用來存放頻繁使用的對(duì)象。一般占據(jù)堆的2/3空間。
老年代的對(duì)象比較穩(wěn)定,所以MajorGC不會(huì)頻繁執(zhí)行。
在進(jìn)行MajorGC前一般都先進(jìn)行了一次MinorGC,使得有新生代的對(duì)象晉身入老年代,導(dǎo)致空間不夠用時(shí)才觸發(fā)。
當(dāng)無法找到足夠大的連續(xù)空間分配給新創(chuàng)建的較大對(duì)象時(shí)也會(huì)提前觸發(fā)一次MajorGC進(jìn)行垃圾回收騰出空間。
MajorGC 是采用標(biāo)記清除算法來清理垃圾
2.1、永久代(jdk1.7前)
指內(nèi)存的永久保存區(qū)域,主要存放Class和Meta(元數(shù)據(jù))的信息,
Class在被加載的時(shí)候被放入永久區(qū)域. 它和存放實(shí)例的區(qū)域不同
GC不會(huì)在主程序運(yùn)行期對(duì)永久區(qū)域進(jìn)行清理,
永久代的區(qū)域會(huì)隨著加載的Class的增多而脹滿,最終拋出OOM異常。
在Java8中,永久代已經(jīng)被移除,被一個(gè)稱為“元數(shù)據(jù)區(qū)”(元空間)的區(qū)域所取代。
2.2、元空間(jdk1.8后)
元空間的本質(zhì)和永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。
不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。
默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。
類的元數(shù)據(jù)放入 native memory, 字符串池和類的靜態(tài)變量放入java堆中. 這樣可以加載多少類的元數(shù)據(jù)就不再由MaxPermSize控制, 而由系統(tǒng)的實(shí)際可用空間來控制.
三、垃圾回收算法
垃圾回收機(jī)制采用算法一覽圖
1、引用算法
使用于新生代,新創(chuàng)建的對(duì)象,默認(rèn)引用值為15
當(dāng)引用值為 0時(shí),該對(duì)象被垃圾回收機(jī)制回收
如果有地方引用,可達(dá)狀態(tài),則加1, 如果沒有地方引用,不可達(dá)狀態(tài),則減1
引用說明,new對(duì)象的時(shí)候創(chuàng)建,在棧中,并指向堆中的對(duì)象
2、復(fù)制算法(MinorGC)
使用于新生代ds0,ds1中,引用算法的引用值到達(dá)了一定高度次數(shù),就會(huì)晉升到ds0,ds1中
新的頻繁使用的對(duì)象都會(huì)在一個(gè)區(qū)內(nèi),要么在ds0,要么在ds1,每次只會(huì)有一個(gè)區(qū)會(huì)有數(shù)據(jù)
假如現(xiàn)在數(shù)據(jù)在ds0中,如果ds0有存活的對(duì)象,把存活的對(duì)象賦值到ds1,然后清空ds0,新的頻繁使用對(duì)象也會(huì)晉升在ds1,假如現(xiàn)在數(shù)據(jù)在ds1中,則反之
3、 標(biāo)記清除算法(MajorGC)
標(biāo)記清除算法
首先掃描一次所有老年代,標(biāo)記出存活的對(duì)象,然后回收沒有標(biāo)記的對(duì)象。
- MajorGC的耗時(shí)比較長(zhǎng),因?yàn)橐獟呙柙倩厥铡?/p>
- MajorGC會(huì)產(chǎn)生內(nèi)存碎片,為了減少內(nèi)存損耗,我們一般需要進(jìn)行合并或者標(biāo)記出來方便下次直接分配。----------- 當(dāng)老年代也滿了裝不下的時(shí)候,就會(huì)拋出OOM(Out of Memory)異常。
- 標(biāo)記壓縮法在標(biāo)記清除基礎(chǔ)之上做了優(yōu)化,把存活的對(duì)象壓縮到內(nèi)存一端,而后進(jìn)行垃圾清理。(java中老年代使用的就是標(biāo)記壓縮法)
4、 java 手動(dòng)回收GC
----Java技術(shù)使用finalize()方法在垃圾收集器將對(duì)象從內(nèi)存中清除出去前,做必要的清理工作
----把對(duì)象賦值為null 在 System.gc(); 即可回收,但不保證100%
public class JVMDemo { public static void main(String[] args) { // 創(chuàng)建了堆內(nèi)存 JVMDemo05 jvmDemo05 = new JVMDemo05(); // 賦值為空后去除該對(duì)象的引用 //jvmDemo05 = null; //手動(dòng)垃圾回收,只有所有線程都無引用的對(duì)象才會(huì)會(huì)GC回收 System.gc(); } /** * obj 方法,垃圾回收前觸發(fā) */ protected void finalize() throws Throwable { System.out.println("gc在回收對(duì)象..."); } }
四、JVM 參數(shù)調(diào)優(yōu)
堆的常用配置參數(shù)參考
-XX:+PrintGC
每次觸發(fā)GC的時(shí)候打印相關(guān)日志-XX:+UseSerialGC
串行回收-XX:+PrintGCDetails
更詳細(xì)的GC日志-Xms
堆初始值-Xmx
堆最大可用值-Xmn
新生代堆最大可用值-XX:SurvivorRatio
用來設(shè)置新生代中eden空間和from/to空間的比例.含以-XX:SurvivorRatio=eden/from=den/to
圖文方式
1、設(shè)置最大堆內(nèi)存
----- 1、堆內(nèi)存默認(rèn)大小4G,我們可以根據(jù)程序大小來指定,默認(rèn)內(nèi)存足夠的情況下不會(huì)觸發(fā)垃圾回收機(jī)制
----- 2、初始的堆大小與最大堆大小設(shè)置為相等,減少垃圾回收次數(shù),如果內(nèi)存一旦達(dá)到初始值,就會(huì)觸發(fā)垃圾回收機(jī)制,頻繁的觸發(fā)垃圾回收機(jī)制影響線程及性能問題
參數(shù):
-Xms
堆初始值-Xmx
堆最大可用值
-Xms512m -Xmx512m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
2、設(shè)置新生代與老年代優(yōu)化參數(shù)
-Xms
堆初始值-Xmx
堆最大可用值-Xmn
新生代大小,一般設(shè)為整個(gè)堆的1/3到1/4左右-XX:SurvivorRatio
設(shè)置新生代中eden區(qū)和from/to空間的比例關(guān)系n/1
參數(shù):
-Xms512m -Xmx512m -Xmn170m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC
3、堆內(nèi)存溢出處理
錯(cuò)誤原因
java.lang.OutOfMemoryError Java heap space 堆內(nèi)存溢出
解決辦法
設(shè)置堆內(nèi)存大小,根據(jù)實(shí)際大小設(shè)置
-Xms
堆初始值-Xmx
堆最大可用值-XX:+HeapDumpOnOutOfMemoryError
內(nèi)存溢出拋出儲(chǔ)存快照
示例:
-Xms512m –Xmx512m -XX:+HeapDumpOnOutOfMemoryError
4、棧內(nèi)存溢出處理
錯(cuò)誤原因
棧溢出 產(chǎn)生于遞歸調(diào)用,循環(huán)遍歷是不會(huì)的,但是循環(huán)方法里面產(chǎn)生遞歸調(diào)用, 也會(huì)發(fā)生棧溢出。
java.lang.StackOverflowError 棧內(nèi)存溢出
解決辦法
設(shè)置線程最大調(diào)用深度
-Xss5m 設(shè)置最大調(diào)用深度,默認(rèn)為1M大小 示例: -Xms512m –Xmx512m -Xss5m -XX:+HeapDumpOnOutOfMemoryError
5、Tomcat內(nèi)存溢出
在catalina.sh 修改JVM堆內(nèi)存大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"
五、 JVM 參數(shù)調(diào)優(yōu)二( GC- 垃圾回收器)
1、串行回收(Serial Collector)
------ 單線程執(zhí)行回收操作,回收期間暫停所有應(yīng)用線程的執(zhí)行,client模式下的默認(rèn)回收器,
jvm參數(shù)
-XX:+UseSerialGC 設(shè)置為串行回收
詳細(xì)參數(shù)
-XX:+PrintGCDetails -Xmx512M -Xms512M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseSerialGC -XX:PermSize=32M
2、并行回收(ParallelGC)
------ ParNew (新生代 )并行回收器在串行回收器基礎(chǔ)上做了改進(jìn),他可以使用多個(gè)線程同時(shí)進(jìn)行垃圾回收,對(duì)于計(jì)算能力強(qiáng)的計(jì)算機(jī)而言,可以有效的縮短垃圾回收所需的實(shí)際時(shí)間。
------ ParallelOldGC (老年代) 回收器也是一種多線程的回收器,和新生代的ParallelGC回收器一樣,也是一種關(guān)往吞吐量的回收器,他使用了標(biāo)記壓縮算法進(jìn)行實(shí)現(xiàn)。
jvm參數(shù)
----ParNew設(shè)置(新生代) XX:+UseParNewGC 設(shè)置為并行回收 XX:ParaleiGCThreads 設(shè)置ParNew回收器工作時(shí)的線程數(shù)量,建議指定為電腦核數(shù)*2 ----ParallelOldGC設(shè)置(老年代) -XX:+UseParallelOldGC 設(shè)置為并行回收 -XX:+ParallelCThread 設(shè)置垃圾收集時(shí)的線程教量, 建議指定為電腦核數(shù)*2
詳細(xì)參數(shù)(UseParNewGC)
-XX:+PrintGCDetails -Xmx512M –Xms512M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParNewGC -XX:PermSize=32M
詳細(xì)參數(shù)(UseParallelGC)
-XX:+PrintGCDetails -Xmx512M -Xms256M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -XX:PermSize=32M
G1回收器了解
G1回收器(Garbage-First)實(shí)在]dk1.7中提出的垃圾回收器,從長(zhǎng)期目標(biāo)來看是為了取代CMS回收器,G1回收器擁有獨(dú)特的垃圾回收策略,G1屬于分代垃圾回收器,區(qū)分新生代和老年代,依然有eden和from/to區(qū),它并不要求整個(gè)eden區(qū)或者新生代、老年代的空間都連續(xù),它使用了分區(qū)算法。
jvm調(diào)優(yōu)總結(jié)
1、初始堆值和最大堆內(nèi)存內(nèi)存越大,吞吐量就越高。
2、最好使用并行收集器,因?yàn)椴⑿惺謾C(jī)器速度比串行吞吐量高,速度快。
3、設(shè)置堆內(nèi)存新生代的比例和老年代的比例最好為1:2或者1:3。
4、減少GC對(duì)老年代的回收。
六、java 代碼中查看大小
public class JvmDemo01 { public static void main(String[] args) throws InterruptedException { byte[] b1 = new byte[1 * 1024 * 1024]; System.out.println("分配了1m"); jvmInfo(); Thread.sleep(3000); byte[] b2 = new byte[4 * 1024 * 1024]; System.out.println("分配了4m"); Thread.sleep(3000); jvmInfo(); } static private String toM(long maxMemory) { float num = (float) maxMemory / (1024 * 1024); DecimalFormat df = new DecimalFormat("0.00");// 格式化小數(shù) String s = df.format(num);// 返回的是String類型 return s; } static private void jvmInfo() { // 最大內(nèi)存 long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println("maxMemory:" + maxMemory + ",轉(zhuǎn)換為M:" + toM(maxMemory)); // 當(dāng)前空閑內(nèi)存 long freeMemory = Runtime.getRuntime().freeMemory(); System.out.println("freeMemory:" +freeMemory+",轉(zhuǎn)換為M:"+toM(freeMemory)); // 已經(jīng)使用內(nèi)存 long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("totalMemory:" +totalMemory+",轉(zhuǎn)換為M"+toM(totalMemory)); } }
idea 設(shè)置參數(shù)
進(jìn)入編輯找到對(duì)應(yīng)的啟動(dòng)類,VM哪填寫JVM參數(shù)就好了
eclipse 設(shè)置參數(shù)
設(shè)置
啟動(dòng)jar 項(xiàng)目設(shè)置參數(shù)
java -jar -Xms20m -Xmx20m xxxxx.jar
其他:壓力測(cè)試工具 JMeter,可測(cè)試接口吞吐量(每秒的并發(fā)量)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot靜態(tài)資源與首頁配置實(shí)現(xiàn)原理深入分析
最近在做SpringBoot項(xiàng)目的時(shí)候遇到了“白頁”問題,通過查資料對(duì)SpringBoot訪問靜態(tài)資源做了總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10Spring中@Autowired和@Resource注解的使用區(qū)別詳解
這篇文章主要介紹了Spring中@Autowired和@Resource注解的使用區(qū)別詳解,@Autowired默認(rèn)根據(jù)type進(jìn)行注入,找到與指定類型兼容的?Bean?并進(jìn)行注入,如果無法通過type匹配到對(duì)應(yīng)的?Bean?的話,會(huì)根據(jù)name進(jìn)行匹配,如果都匹配不到則拋出異常,需要的朋友可以參考下2023-11-11Spring數(shù)據(jù)庫連接池url參數(shù)踩坑及解決
這篇文章主要介紹了Spring數(shù)據(jù)庫連接池url參數(shù)踩坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09SpringBoot讀取多環(huán)境配置文件的幾種方式
這篇文章主要給大家介紹了SpringBoot讀取多環(huán)境配置文件的幾種方式,文章通過代碼示例介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-10-10Spring Boot Admin(監(jiān)控工具)的使用
今天我們將會(huì)講解一個(gè)優(yōu)秀的監(jiān)控工具Spring Boot Admin。 它采用圖形化的界面,讓我們的Spring Boot管理更加簡(jiǎn)單,需要的朋友可以參考下2020-02-02