java 內存模型
為什麼現在開發中強調JVM
1、當前的互聯網開發環境有直接的關係:已經不再單獨的面對傳統的一台主機運行一些程序,而後在進行簡單的維護,現在講究的是:高並發、分佈式、高可用,對於程序的調優裏面就需要去考慮JVM參數設計、JUC的使用。
【面試必問內容】Java架構師(基礎能力):框架設計+通訊+多線程(JUC)+JVM+數據結構+良好的結構設計(需要大量的代碼基礎)
2、需要清楚內存模型、虛擬機分類、運行模式
3、不適當的JVM運行狀態,有可能會浪費你的電腦性能、良好的JVM調優,可以增加你電腦處理的負載;
4、GC處理流程以及常見的算法(JDK1.8、JDK1.9-JDK1.11)
Java程序執行流程
雙親加載機制:系統類由系統類加載負責,而自定義類由其他的類加載器負責。
Java運行時數據區(內存問題)
1、方法區:最重要的內存區域,多線程內存共享,保存了類的信息(名稱、成員、接口、父類),反射機制是重要組成部分,動態進行類操作的實現。
2、堆內存(Heap):保存對象的真實信息,該內存牽扯到釋放問題(GC);
3、棧內存(Stack):線程的私有空間,在每一次進行方法調用的時候都會存在有棧幀,採用先進後出的設計原則;
3.1、本地變量表:局部參數或形參,允許保存有32位的插槽(Solt),如果超過了32位的長度,需要開闢兩個連續性的插槽(long、double)–volatile關鍵字問題
3.2、操作數棧:執行所有的方法計算操作
3.3、常量池引用:String類、Integer類實例
3.4、返回地址:方法執行完畢後的恢復執行點
4、程序計數器:執行指令的一個順序編碼,該區域的所佔比率幾乎可以忽略;
5、本地方法棧:與棧內存功能類似,區別在於是為本地方法服務的
JVM分類
Java是直接通過指針進行的程序訪問,所以它沒有採用句柄的形式操作,這樣使得程序的性能更高。
傳統意義上來講,JVM一共分為三種(虛擬機是一個公共標準)
【SUN】從JDK1.2開始使用了HotSpot虛擬機標準(2006年開源,利用C++實現、一些JNI部分使用的是系統提供C程序實現的、JIT即時編譯器);
【BEA】使用了JRockit虛擬機標準,例如WebLogic;
【IBM】開發了JVM』s(J9)虛擬機;
Oracle後來通過收購得到了:SUN與BEA,那麼Oracle有了兩個虛擬機標準(不可能浪費兩個研發團隊去干相同的事情);
HotSpot
java version "13.0.1" 2019-10-15
Java(TM) SE Runtime Environment (build 13.0.1+9)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
虛擬機提供三種處理模式:
1、混合模式:java -version
2、【禁用JIT】純解釋模式:java -Xint -version
3、純編譯模式:java -Xcomp -version
運行模式:
1、【client】客戶端運行:啟動速度快,但中間程序的執行慢,佔用內存小;
2、【server】服務器端運行:啟動速度慢,佔用內存多,執行效率高;
3、修改路徑:
控制台:cd /Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home/lib
Java內存模型(取消伸縮區)(重要)
1、合理的內存模型可以使GC的性能更加強大,不必太大的浪費服務器的性能,從而減少阻塞所帶來的程序的性能影響。
例:你現在收拾屋子,基本上會有兩類收拾方法:
方式一:簡單的進行物品的碼放以及打掃衛生,時間短;
方式二:房屋裝修與改造,時間長。
2、Java中數據保存的內存位置:堆內存(調優、原理);
最需要強調的就是JDK1.8之後所帶來的內存結構改變以及GC的策略提升;
1.8以後
JVM1.8以前
當內存不足的時候就需要進行伸縮區的控制,當內存充足的時候就需要考慮將伸縮區所佔用的內存釋放掉(收起),一定會造成額外的計算性能的影響,導致程序的整體性能下降。既然已經確定了當前可能是高並發的訪問,並且我的程序獨立的在一台服務器上,那麼這台服務器的資源就應該全部給我去使用。
public static void main(String[] args) { System.out.println("MAX_MEMORY;"+byteToM(Runtime.getRuntime().maxMemory())); System.out.println("TOTAL_MEMORY;"+byteToM(Runtime.getRuntime().totalMemory())); } public static double round(double num,int scale) { return Math.round(Math.pow(10, scale)*num)/Math.pow(10, scale); } public static double byteToM(long num) { return round(num/1024/1024, 2); }
——初始打印結果
MAX_MEMORY;4096.0
TOTAL_MEMORY;258.0
我現在的電腦內存為16G,所以會發現默認的內容:
MaxMemory:整體電腦內存的1/4
ToTalMemory:整體電腦的1/64
伸縮區的空間:MaxMemory – TotalMemory = 空間好大
程序執行的時候設置有響應的執行參數(-Xmx10G -Xms10G)(取消伸縮區):
-Xmx:分配的最大初始化內存
-Xms:最大的分配內存
java -Xmx大小單位 -Xms大小單位 類文件
——取消伸縮區打印結果
MAX_MEMORY;10240.0
TOTAL_MEMORY;10240.0
GC處理流程(重要)
1、對象的實例化需要依據關鍵字new完成,所有新對象都會在伊甸園區開闢,若果伊甸園區內存不足會發生MinorGC。
Member mem=new Member();很小,直接保存在伊甸園;
2、伊甸園區不是無限大的,所有肯定有些對象執行了N次MinorGC後還會存在,那麼這些對象將進入到存活區(存活區有兩個,一個負責保存存活對象,一個負責晉陞,永遠都有一個是空的內存);
3、如果經歷過若干次的MinorGC回收處理之後發現空間依然不夠使用的,那麼則進行老年代的GC回收,執行了一個MajorGC(Full GC,性能很差),如果可以回收空間,則繼續進行MinorGC;
4、如果MajorGC失敗,則繼續內存已經佔用完滿,則拋出OOM異常。
5、如果新創建的對象的空間佔用過大將直接保存到老年代中。
String str="hello"; for (int x=0;x<Integer.MAX_VALUE;x++){ str+=str.intern(); }
添加一些參數:-XX:+PrintGCDetails
JDK 1.8的時候默認會根據系統的不同而選擇不同個GC回收策略
JDK 1.9~默認的採用GC操作就是G1
內存回收算法
1、年輕代回收算法
1.1、「複製」清理算法,將保留的對象複製到存活區之中,存活區的內容會保存到老年代之中;
1.2、伊甸園區總是會有大量的新對象產生,所以HotSpot虛擬機使用了BTP(單一CPU的時代所有對象依次保存)、TLAB(拆分不同的塊,依據CPU的核心個數拆分)技術形式進行處理
2、老年代回收算法
2.1、「標記-清除」算法:先進行對象的第一次標記,在這段時間之內會暫停程序的執行(如果標記的時間長或者對象的內容過多),這個暫停的時間就會長
2.1.1、就會產生串行標記、並行標記使用問題
2.2、「標記-壓縮」算法:將零散的內存空間進行整理重新集合再分配
G1算法
1、支持大內存(4-64G)、支持多CPU,減少停頓時間,可以保證並髮狀態下的程序的執行。
使用方法
JDK1.8:-XX:+UseG1GC
JDK 11之後默認就是G1回收器,對於其他的回收算法實際上就可以忽略掉了
Tomcat調優
使用內存的調優
路徑:/tomcat/bin
編輯:catalina.sh
追加配置參數:JAVA_OPTS=”-Xms4096m -Xmx4096m -Xss1024k -XX:+UseG1GC”