JVM體系結構,類加載器,Native,JVM分區,OOM,GC算法等

1、JVM的位置

在這裏插入圖片描述

2、JVM體系結構

在這裏插入圖片描述

3、類加載器及雙親委派機制

類加載階段分爲加載、連接、初始化三個階段,而加載階段需要通過類的全限定名來獲取定義了此類的二進制字節流。Java特意把這一步抽出來用類加載器來實現。
類加載器:
作用:加載Class文件
在這裏插入圖片描述
1、啓動類(根)加載器,屬於虛擬機自身的一部分,用C++實現的,主要負責加載%JAVA_HOME%\bin目錄下的所有jar包,或者是-Xbootclasspath參數指定的路徑;等於是所有類加載器的爸爸。
2、擴展類加載器是Java實現的,獨立於虛擬機,負責加載%JAVA_HOME%\bin\ext目錄下的所有jar包,或者是java.ext.dirs參數指定的路徑;
3、應用程序類加載器,負責加載用戶類路徑上所指定的類庫,如果應用程序中沒有自定義加載器,那麼此加載器就爲默認加載器。
4、虛擬機自帶的加載器
在這裏插入圖片描述

雙親委派機制:安全
原理
1、類加載器收到類加載的請求
2、將這個請求向上委託給父類加載器去完成,一直向上委託,直到啓動類加載器(BootstrapClassLoader)
3、啓動類加載器檢查是否能夠加載當前這個類,能加載就結束,使用當前的加載器,否則,拋出異常,通知子加載器進行加載
4、重複步驟 3

舉個例子:
大家所熟知的Object類,直接告訴大家,Object默認情況下是啓動類加載器進行加載的。假設我也自定義一個Object,並且制定加載器爲自定義加載器。現在你會發現自定義的Object可以正常編譯,但是永遠無法被加載運行。
這是因爲申請自定義Object加載時,總是啓動類加載器,而不是自定義加載器,也不會是其他的加載器。

4、沙箱安全機制:瞭解

5、Native、方法區

凡是帶了native關鍵字的方法,說明Java作用範圍達不到了,會去調用底層C語言的庫!
會進入本地方法棧
調用本地方法接口 JNI
JNI的作用:擴展Java的使用,融合其他不同語言爲Java所用。最初:C、C++
Java誕生的時候C、C++橫行,要想立足,必須要有調用C、C++的程序
它在內存中專門開闢了一塊區域,Native Method Stack ,登記Native方法
在最終執行的時候加載本地方法庫中的方法通過JNI

比如Java程序驅動打印機等,調用其他接口,如Socket,WebService,http
在這裏插入圖片描述

PC寄存器
程序計數器:Programe Counter Register
每個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向一條指令的地址,也就是將要執行的指令代碼),在執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不計

方法區
Method Area 方法區
方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法,如構造函數,接口代碼也在此定義,簡單說,所有定義的方法的信息也都保存在此區域,此區域屬於共享區間
靜態變量,常量,類信息(構造方法,接口定義),運行時的常量池,存在方法區中,但是實例變量存在堆內存中,和方法區無關
static 、final 、class 、運行時常量池 的東西存在方法區中,就存這麼點東西,其他東西跟它無關

6、深入理解一下棧

棧:數據結構
程序 = 數據結構 + 算法

棧: 先進後出、後進先出:桶
隊列: 先進先出(FIFO:First In First Out)

喝多了吐就是棧,喫多了拉就是隊列

爲什麼main()先執行,最後結束

棧: 棧內存,主管程序的運行,生命週期和線程同步;
線程結束,棧內存也就釋放,對於棧來說,不存在垃圾回收問題
一旦線程結束,棧就Over!

棧運行原理:棧幀
在這裏插入圖片描述
程序正在執行的方法引用,一定在棧的頂部,棧中的方法是方法區的引用

棧滿了:StackOverflowError

棧 + 堆 + 方法區:交互關係

棧中存的是什麼?怎麼存?
中存的是基本數據類型和堆中對象的引用,在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處:)爲什麼不把基本類型放堆中呢?因爲其佔用的空間一般是1~8個字節——需要空間比較少,而且因爲是基本類型,所以不會出現動態增長的情況——長度固定,因此棧中存儲就夠了,如果把他存在堆中是沒有什麼意義的(還會浪費空間,後面說明)。可以這麼說,基本類型和對象的引用都是存放在棧中,而且都是幾個字節的一個數,因此在程序運行時,他們的處理方式是統一的。對象本身不存放在棧中,而是存放在堆(new出來的對象)或者常量池中(字符串常量對象存放的常量池中),局部變量【注意:(方法中的局部變量使用final修飾後,放在堆中,而不是棧中)】

:存放使用new創建的對象,全局變量

內存中對象實例化的過程
在這裏插入圖片描述
Pet.java

package com.fang;
public class Pet {
    String name;
    int age;
    
    //無參構造
    
    public void shout(){
        System.out.println("叫了一聲");
    }
}

Application.java

package com.fang;
public class Application {
    public static void main(String[] args) {
        Pet dog = new Pet();
        dog.name = "旺財";
        dog.age = 3;
        dog.shout();
        System.out.println(dog.name);
        System.out.println(dog.age);
    }
}

7、走進HotSpot和堆

三種JVM
Sun公司的 HotSpot
BEA JRockit
IBM J9VM

我們學習都是: HotSpot

Heap,一個JVM只有一個堆內存,堆內存的大小是可以調節的
類加載器讀取了類文件後,一般會把什麼東西放到堆中?類,方法,常量,變量,保存我們所有引用類型的真實對象;

堆內存中還要細分爲三個區域:
新生區(伊甸園區) Young/New
養老區 old
永久區(元空間) Perm
在這裏插入圖片描述

8、新生區、永久區、堆內存調優

新生區
類:誕生和成長的地方,甚至死亡;
伊甸園,所有的對象都是在伊甸園區new出來的
倖存者區(0區,1區)from ... to ...
老年區

在這裏插入圖片描述
真理:經過研究,99%的對象都是臨時對象

永久區

這個區域常駐內存的,用來存放JDK自身攜帶的Class對象,Interface元數據,存儲的是Java運行時的一些環境或類信息,這個區域不存在垃圾回收!關閉VM虛擬機就會釋放這個區域的內存

一個啓動類,加載了大量的第三方jar包,Tomcat步數了太多的應用,大量動態生成的反射類。不斷的被加載,直到內存,滿,就會出現OOM;

jdk1.6之前:永久代,常量池是在方法區;
jdk1.7		:永久代,但是慢慢的退化了,叫做**去永久代**,常量池在堆中
jdk1.8之後:無永久代,常量池在元空間

在這裏插入圖片描述
在這裏插入圖片描述
內存調優
// -Xms 設置初始化內存分配大小 1 / 64
// -Xmx 設置最大分配內存,默認 1 / 4
// -XX:+PrintGCDetails //打印垃圾回收信息
// -XX:+HeapDumpOnOutOfMemory //OOM Dump
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
然後運行,我們可以算一筆賬:
305664K(新生代) + 699392K(老年代) = 1005056K = 981.5MB

所以說明了,永久代邏輯上存在,物理上不存在,可以認爲堆內存就是指的新生代和老年代的總大小,GC垃圾回收也是在新生代(伊甸園區)和老年區進行的,永久代和GC沒關係

再來看一下OOM
在這裏插入圖片描述
在這裏插入圖片描述
這裏運行結果可以看出來垃圾回收機制,前幾次只是在新生代的輕量級的GC,後面第四次的時候發現伊甸園區和倖存區都滿了,有了重量級的Full GC,後面又一次輕GC,又滿了,又進行重GC,重複,直到老年區也已經滿了,然後就拋出OOM 的異常(OutOfMemoryError),堆空間滿了

9、使用JProfiler工具分析OOM的原因

首先安裝和配置Jprofiler,可以去參考視頻使用JProfiler工具分析OOM的原因
在一個項目中,突然出現了OOM故障,該如何排除?研究爲什麼出錯
能夠看到代碼第幾行出錯:內存快照分析工具:MAT、Jprofiler
Debug,一行行分析代碼!
MAT、Jprofiler作用:
分析Dump內存文件,快速定位內存泄漏;
獲得堆中的數據
獲得大的對象

內存調優
// -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
內存調優之後,可以Dump下來一些內存快照文件,如下顯示:
在這裏插入圖片描述
從項目目錄找到它,然後打開,用Jprofiler打開它
在這裏插入圖片描述
在Jprofiler裏面可以看到一些信息,明顯這裏ArrayList出問題了
在這裏插入圖片描述
再看線程Dump,看main線程哪裏出了問題
在這裏插入圖片描述

10、GC介紹之引用計數法

在這裏插入圖片描述
JVM在進行GC時,並不是對這三個區域統一回收。大部分時候,回收都是新生代
新生代
倖存區(from , to)
老年區
GC兩種類型:輕GC(普通的GC),重GC(全局GC)

GC題目:
JVM的內存模型和分區,詳細到每個區放什麼?
堆裏面的分區有哪些?Eden,from,to,老年區,說說他們的特點!
GC的算法有哪些?標記清除法、標記壓縮、複製算法、引用計數器,怎麼用的?
輕GC和重GC分別在什麼時候發生?

引用計數法:通過在對象頭中分配一個空間來保存該對象被引用的次數。如果該對象被其它對象引用,則它的引用計數加一,如果刪除對該對象的引用,那麼它的引用計數就減一,當該對象的引用計數爲0時,那麼該對象就會被回收
在這裏插入圖片描述

11、GC之複製算法:

	**複製算法將內存劃分爲兩個區間,在任意時間點,所有動態分配的對象都只能分配在其中一個區間(稱爲活動區間),而另外一個區間(稱爲空閒區間)則是空閒的。**
     當有效內存空間耗盡時,JVM將暫停程序運行,開啓複製算法GC線程。接下來**GC線程會將活動區間內的存活對象,全部複製到空閒區間,且嚴格按照內存地址依次排列,與此同時,GC線程將更新存活對象的內存引用地址指向新的內存地址。**

在這裏插入圖片描述
在這裏插入圖片描述
好處:沒有內存碎片,彌補了標記/清除算法中,內存佈局混亂的缺點
壞處:浪費了內存空間:多了一半空間永遠是空的(倖存區to區),假設對象100%存活(極端情況),那麼我們需要將所有對象都複製一遍,並將所有引用地址重置一遍。複製這一工作所花費的時間,在對象存活率達到一定程度時,將會變的不可忽視。

複製算法最佳使用場景:對象存貨度較低的時候,新生區。

12、GC回收算法之標記清除壓縮算法

標記清除算法:
在這裏插入圖片描述
標記清除算法的優缺點:
優點:不需要額外的空間!
缺點:兩次掃描,嚴重浪費時間,會產生內存碎片。

標記壓縮:再優化
在這裏插入圖片描述

標記清除壓縮:先標記清除幾次(產生了內存碎片了),再標記壓縮(整理產生的內存碎片)

13、GC算法總結

內存效率:複製算法 > 標記清除算法 > 標記壓縮算法(時間複雜度)
內存整齊度:複製算法 = 標記壓縮算法 > 標記清除算法
內存利用率:標記壓縮算法 = 標記清除算法 > 複製算法

思考:有沒有最優的垃圾回收算法?
答案:沒有,沒有最好的算法,只有最合適的算法---->GC:分代蒐集算法

年輕代:存活率低,複製算法
老年代:區域大:存活率高,標記清除(內存碎片不是太多) + 標記壓縮混合實現

既然這三種算法都各有缺陷,高人們自然不會容許這種情況發生。因此,高人們提出可以根據對象的不同特性,使用不同的算法處理,類似於蘿蔔白菜各有所愛的原理。於是奇蹟發生了,高人們終於找到了GC算法中的神級算法-----分代蒐集算法

以上JVM學習的Line

學習《深入理解Java虛擬機》 周志明,掌握學習JVM的方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章