簡單說說JVM堆區(qū)的相關知識
一、堆概述
- 一個jvm實例(進程)只存在一個堆內存,堆也是java內存管理的核心區(qū)域。
- java 堆區(qū)在jvm啟動時即被創(chuàng)建,其空間大小也就被確定了
- 《java虛擬機規(guī)范》規(guī)定,堆可以處于物理上不連續(xù)的內存空間,但在邏輯上它應該被稱為連續(xù)的
- 所有線程共享java堆,在這里和可以劃分線程私有的緩沖區(qū)(tlab)
- 所有對象實例以及數(shù)組都應在運行時分配在堆中
- 方法結束后,堆中的對象不會馬上被移除,僅僅在垃圾收集時候才會被移除
- 堆是gc執(zhí)行垃圾回收的重點區(qū)域
1.1 堆內存細分
現(xiàn)代垃圾收集器大部分基于分代收集理論設計,堆空間細分為:
- java 7 之前堆內存邏輯分為:新生區(qū)+老年區(qū)+永久區(qū)
- java 8 之后內存邏輯上分為:新生區(qū)+老年區(qū)+元空間
使用下面命令設置堆空間初始化 10m,最大空間 10m
-Xms10m -Xmx10m
使用java visual 查看 visual gc
可以看出通過參數(shù)設置的內存大小 只與新生代(Eden+s0+s1 ),老年代有關,而在邏輯上還要加上元空間。
1.2 堆空間大小的設置
1.2.1 通過參數(shù)設置
-Xms 用來設置堆空間(年輕代+老年代)的初始內存大小 -X 是jvm的運行參數(shù) ms 是memory start -Xmx 用來設置堆空間(年輕代+老年代)的初始內存大小
- 一旦堆區(qū)中的內存大小超過“-Xmx”所指定的最大內存時,將會拋出OutOfMemoryError異常。
- 通常會將 -Xms 和 -Xmx 兩個參數(shù)配置相同的值,其目的是為了能夠在java垃圾回收機制清理完堆區(qū)后不需要重新分隔計算堆區(qū)的大小,從而提高性能
1.2.2 默認空間大小
- 初始化內存大小為物理內存的 1/64
- 最大內存大小為物理內存大小 1/4
使用一下代碼查看 當前jvm初始化內存與最大內存
/** * @program: jvmDemo * @description: * @author: wfg * @create: 2021-06-14 10:40 */ public class Test9 { public static void main(String[] args) { //返回jvm中的內存總量(字節(jié)) long initialMemory = Runtime.getRuntime().totalMemory()/1024/1024; //虛擬機將嘗試使用最大堆內存 long maxMemory = Runtime.getRuntime().maxMemory()/1024/1024; System.out.println("-Xms:"+initialMemory+"m"); System.out.println("-Xmx:"+maxMemory+"m"); System.out.println("系統(tǒng)大?。?+initialMemory*64/1024+"G"); System.out.println("系統(tǒng)大?。?+maxMemory*4/1024+"G"); } }
結果(本機運行內存為 8g)
1.2.3 通過參數(shù)設置堆空間大小后內存不一致問題
設置300
查看
/** * @program: jvmDemo * @description: * @author: wfg * @create: 2021-06-14 10:40 */ public class Test9 { public static void main(String[] args) { //返回jvm中的內存總量(字節(jié)) long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024; //虛擬機將嘗試使用最大堆內存 long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024; System.out.println("-Xms:" + initialMemory + "m"); System.out.println("-Xmx:" + maxMemory + "m"); } }
結果
分析
在vm參數(shù)設置里面加上
-XX:+PrintGCDetails
再次運行程序查看
原理
新生代的s0 和 s1 只能有一個生效
1.3 年輕代與年老代
- 存儲在jvm中的java對象可以劃分為兩類:
一類是生命周期較短的瞬時對象。
另外一類是對象生命周期非常長。
- jvm堆區(qū)再次進行細分可以分為 年輕代與老年代
- 其中年輕代可以劃分為Eden空間,survivor0空間和survivor1空間(有時也叫 from區(qū),to區(qū))
- 新生代與老年代空間大小 默認是 1:2
可以通過 -XX:NewRation
設置新生代與老年代的比例,默認值是2.(一般都不會去設置)
- Eden與s0,s1 的內存默認分配比例為 8:1:1
1.4 對象分配過程
1.new的對象先放伊甸區(qū),此區(qū)有大小限制
2.當伊甸區(qū)滿的時候,程序需要創(chuàng)建時,jvm的垃圾回收將對伊甸園區(qū)進行垃圾回收(minor gc)
3.然后將伊甸園中的剩余對象移動到辛存者0
4.如果再次觸發(fā)垃圾回收,上次幸存下來的放到幸存者0區(qū),沒有回收,就會放到幸存者1區(qū)
5.再次經(jīng)歷垃圾回收會重新放回辛存者0區(qū)
6.當在辛存者區(qū)達到15次時,就可以去老年區(qū)了
可以設置參數(shù):-XX:MaxTenuringThreshold=
7.當養(yǎng)老區(qū)內存不足時,再次觸發(fā)GC:major GC,進行養(yǎng)老區(qū)的內存處理
8.若養(yǎng)老區(qū)進行處理后,依然無法進行對象的保存,就會產(chǎn)生00m異常
java.lang.outofMemoryError:java heap space
9.總結
- 針對幸存者s0,s1區(qū)總結:復制之后有交換誰空誰是to
- 垃圾回收:頻繁在新生區(qū)收集,很少在養(yǎng)老區(qū)收集,幾乎不再永久區(qū)/元空間收集
10.流程
當Eden滿時,會觸發(fā)MinorGC算法來回收memory,旨在清理掉再無引用的數(shù)據(jù)(在內存里是Tree),意圖存儲到S0. 若此時S0也滿了,會再次MinorGC意圖回收S0無引用的數(shù)據(jù),把有引用的數(shù)據(jù)移動到S1。如果S1夠用,此時會清空S0;如果S1滿了,會回滾剛存入S1的數(shù)據(jù),直接把本次GC的數(shù)據(jù)存入Old區(qū),S0保持剛剛MinorGC時的狀態(tài)。延伸:如果Old也滿了,會觸發(fā)MajorGC,如果還是不夠,則存入Permanent Generate,不幸這里也滿了,會在允許的范圍內按照內置的規(guī)則自動增長,可能不會發(fā)生GC,也可能會。當增長的量不夠存時,會觸發(fā)Full GC。若FullGC后還是不夠存,自動增長的量也超過了允許的范圍,則發(fā)生內存溢出。還有一種情況,就是分配的線程棧處于很深的遞歸或死循環(huán)時,會發(fā)生棧內存溢出。
二、對象分配過程:Tlab
2.1 為什么要有tlab
- 堆區(qū)是線程共享的,任何線程都可以訪問到堆區(qū)中的共享數(shù)據(jù)
- 由于對象實例的創(chuàng)建在jvm中非常頻繁,因此在并發(fā)環(huán)境下從堆區(qū)中劃分內存空間是不安全的
- 而加鎖會影響分配速度
2.2什么是tlab
- 從內存模型而不是垃圾收集角度,對eden區(qū)域進行劃分,jvm為每個線程分配了一個私有緩存區(qū)域
- 多線程同時分配內存時,使用tlab可以避免非線程安全問題,同時還能夠提升內存分配的吞吐量,因此我們將其稱為 快速分配策略
- jvm將tlab作為內存分配的首選
- 在程序中可以通過參數(shù)
-XX:UseTLAB
設置是否開啟(默認是開啟的) - tlab僅占有eden空間大小的1%,可以通過
-XX:TLABWasteTargetPercent
設置tlab空間所占用eden空間的大小 - 使用tlab空間分配內存失敗時,jvm會使用加鎖機制確保操作的原子性
對象分配流程
三、堆空間常用參數(shù)設置
-
-XX:+PrintFlagsInitial
: 查看所有參數(shù)的默認初始值 -XX:+PrintFlagFinal
: 查看所有參數(shù)的最終值-Xms:
:初始化堆空間內存(默認為物理內存的1/64)-Xmx
: 最大堆空間內存(默認為物理內存的1/4)-Xmn
:設置新生代的大小(初始值及最大值)-XX:NewRatio
: 配置新生代與老年代在堆結構的占比-XX:SurvivorRatio
:設置新生代中 eden和s0、s1空間的比例-XX:MaxTenuringThreshold
:設置新生代垃圾最大的年齡-XX:+PrintGCDetails
:輸出詳細的gc處理日志-
XX:+PrintGC
:輸出簡要的gc處理日志
四、堆是分配對象的唯一選擇嗎
- 隨著jit編譯期的發(fā)展與逃逸分析技術成熟,棧上分配與標量替換優(yōu)化技術導致所有對象都分配到堆上不那么絕對了
- 如果經(jīng)過逃逸分析后發(fā)現(xiàn),一個對象并沒有逃逸出方法的話,那么可能被優(yōu)化成棧上分配
4.1 逃逸分析
- 當一個對象在方法中被定義后,對象只在方法內部使用,則認為沒有發(fā)生逃逸。
- 當一個對象在方法中被定義后,被外部方法所引用,則認為發(fā)生逃逸了
判斷逃逸的方法:看new 的對象實體是否有可能在方法外被調用
4.2 代碼優(yōu)化
- 棧上分配。將堆分配轉化為棧分配。
- 同步省略。如果一個對象被發(fā)現(xiàn)只能從一個線程被訪問到,那么對于這個對象的操作可以不考慮同步
- 分離對象或標量替換。有的對象可能不需要作為一個連續(xù)的內存結構存在也可以被訪問到,那么對象的部分可以不存儲在內存,而是存儲在cpu寄存器中。
- 標量是指一個無法在分解成更小的數(shù)據(jù),相對的其它還可以在分解的稱為聚合量
- 在jit階段進行逃逸分析,發(fā)現(xiàn)對象不會被外界訪問(沒有逃逸發(fā)生),經(jīng)過jit優(yōu)化,就回把對象分解為成員變量來代替,這個過程就是標量替換
到此這篇關于簡單說說JVM堆區(qū)的相關知識的文章就介紹到這了,更多相關JVM堆區(qū)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決@RequestMapping和@FeignClient放在同一個接口上遇到的坑
這篇文章主要介紹了解決@RequestMapping和@FeignClient放在同一個接口上遇到的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07springBoot熱部署、請求轉發(fā)與重定向步驟詳解
這篇文章主要介紹了springBoot熱部署、請求轉發(fā)與重定向,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06SpringBoot+thymeleaf+Echarts+Mysql 實現(xiàn)數(shù)據(jù)可視化讀取的示例
本文主要介紹了SpringBoot+thymeleaf+Echarts+Mysql 實現(xiàn)數(shù)據(jù)可視化讀取的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04Java實現(xiàn)多選批量刪除功能(vue+Element)
這篇文章主要為大家詳細介紹了Java實現(xiàn)多選批量刪除功能,包括前端vue實現(xiàn)代碼文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08