主頁 >  其他 > 花了好幾個晚上整理的JVM知識點,吐血獻出(一)

花了好幾個晚上整理的JVM知識點,吐血獻出(一)

2021-10-28 09:13:16 其他

目錄

一、JVM記憶體模型

記憶體劃分

物件創建

常量池

二、類加載

類加載程序

類加載生命周期

加載

驗證

準備

決議

初始化

創建物件

類加載器

類加載器加載類的大致步驟

雙親委派模式

如何破壞雙親委派模式

三、垃圾回識訓制

什么是垃圾回收

MinorGC和MajorGC

垃圾判斷演算法

參考計數器法

根搜索演算法

垃圾回收演算法

標記-清除

復制演算法

標記-整理(標記-壓縮)

分代演算法

新生代如何進入老年代

如何觸發FullGC

記憶體溢位和記憶體泄漏

四、垃圾收集器

Serial垃圾收集器

ParNew垃圾收集器

Parallel Scavenge 垃圾收集器

Serial Old垃圾收集器

Parallel Old垃圾回收器

CMS收集器


一、JVM記憶體模型

記憶體劃分

JVM記憶體共分為堆、虛擬機堆疊,方法區,本地方法堆疊、程式計數器(暫存器),

  • 堆:被所有執行緒共享的一塊記憶體區域,在虛擬機啟動的時候創建,用于存放物件實體,
  • 虛擬機堆疊:是執行緒私有的,每個方法在執行的時候都會創建一個堆疊幀,堆疊幀存盤了區域變數,運算元堆疊,動態鏈接,方法回傳地址,
  1. 區域變數表:

    區域變數表主要存放了編譯器可知的各種資料型別(booleanbytecharshortintfloatlongdouble)、物件參考(reference型別,它不同于物件本身,可能是一個指向物件起始地址的參考指標,也可能是指向一個代表物件的句柄或其他與此物件相關的位置)和returnAddress型別,區域變數表所需的記憶體空間在編譯期確定,當進入一個方法時,方法在堆疊幀中所需要分配的區域變數控制元件是完全確定的,不可動態改變大小,

  2. 運算元堆疊:

    后進先出LIFO,最大深度由編譯期確定,堆疊幀剛建立使,運算元堆疊為空,執行方法操作時,運算元堆疊用于存放JVM從區域變數表復制的常量或者變數,提供提取,及結果入堆疊,也用于存放呼叫方法需要的引數及接受方法回傳的結果,

    運算元堆疊可以存放一個jvm中定義的任意資料型別的值,

    在任意時刻,運算元堆疊都一個固定的堆疊深度,基本型別除了longdouble占用兩個深度,其它占用一個深度

  3. 動態連接:

    每個堆疊幀都包含一個指向運行時常量池中該堆疊幀所屬方法的參考,持有這個參考是為了支持方法呼叫程序中的動態連接,Class檔案的常量池中存在有大量的符號參考,位元組碼中的方法呼叫指令就以常量池中指向方法的符號參考為引數,這些符號參考,一部分會在類加載階段或第一次使用的時候轉化為直接參考(如finalstatic域等),稱為靜態決議,另一部分將在每一次的運行期間轉化為直接參考,這部分稱為動態連接,

  4. 方法回傳地址:

    當一個方法被執行后,有兩種方式退出該方法:執行引擎遇到了任意一個方法回傳的位元組碼指令(lreturnfreturndreturn以及areturn)或遇到了例外,并且該例外沒有在方法體內得到處理,無論采用何種退出方式,在方法退出之后,都需要回傳到方法被呼叫的位置,程式才能繼續執行,方法回傳時可能需要在堆疊幀中保存一些資訊,用來幫助恢復它的上層方法的執行狀態,一般來說,方法正常退出時,呼叫者的PC計數器的值就可以作為回傳地址,堆疊幀中很可能保存了這個計數器值,而方法例外退出時,回傳地址是要通過例外處理器來確定的,堆疊幀中一般不會保存這部分資訊,

    方法退出的程序實際上等同于把當前堆疊幀出堆疊,因此退出時可能執行的操作有:恢復上層方法的區域變數表和運算元堆疊,如果有回傳值,則把它壓入呼叫者堆疊幀的運算元堆疊中,調整PC計數器的值以指向方法呼叫指令后面的一條指令,

  • 方法區:執行緒共享的一塊記憶體區域,用于存盤已經被虛擬機加載的類資訊,常量,靜態變數等,

  • 本地方法堆疊:執行緒私有的,與虛擬機堆疊類似,主要為虛擬機使用到的Native方法服務,

  • 程式計數器:執行緒私有的,程式計數器指當前正在執行的位元組碼的行號,如果是Native方法,則為空,

物件創建

1、類加載檢查: 虛擬機遇到一條 new 指令時,首先會去檢查這個指令的引數是否能在常量池中定位到這個類的符號參考,并且檢查這個符號參考代表的類是否已被加載過、決議和初始化過,如果沒有,那必須先執行相應的類加載程序,

2、分配記憶體: 在類加載檢查通過后,接下來虛擬機將為新生物件分配記憶體,物件所需的記憶體大小在類加載完成后便可確定,為物件分配空間的任務等同于把一塊確定大小的記憶體從 Java 堆中劃分出來,分配方式有 “指標碰撞” “空閑串列” 兩種,選擇那種分配方式由 Java 堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定,

記憶體分配的兩種方式:

選擇以上兩種方式中的哪一種,取決于 Java 堆記憶體是否規整,而 Java 堆記憶體是否規整,取決于 GC 收集器的演算法是"標記-清除",還是"標記-整理"(也稱作"標記-壓縮"),值得注意的是,復制演算法記憶體也是規整的,

3、初始化零值: 記憶體分配完成后,虛擬機需要將分配到的記憶體空間都初始化為零值(不包括物件頭),這一步操作保證了物件的實體欄位在 Java 代碼中可以不賦初始值就直接使用,程式能訪問到這些欄位的資料型別所對應的零值,

4設定物件頭: 初始化零值完成之后,虛擬機要對物件進行必要的設定,例如這個物件是那個類的實體、如何才能找到類的元資料資訊、哈希值、 gc分代年齡 、鎖狀態標志、 執行緒持有的鎖, 這些資訊存放在物件頭中, 另外,根據虛擬機當前運行狀態的不同,如是否啟用偏向鎖等,物件頭會有不同的設定方式,

5、執行 init 方法: 在上面作業都完成之后,從虛擬機的視角來看,一個新的物件已經產生了,但從 Java 程式的視角來看,物件創建才剛開始,<init> 方法還沒有執行,所有的欄位都還為零,所以一般來說,執行 new 指令之后會接著執行 <init> 方法,把物件按照程式員的意愿進行初始化,這樣一個真正可用的物件才算完全產生出來,

常量池

Java 基本型別的包裝類的大部分都實作了常量池技術,即Byte,Short,Integer,Long,Character,Boolean;這5種包裝類默認創建了數值[-128127]的相應型別的快取資料,但是超出此范圍仍然會去創建新的物件,

兩種浮點數型別的包裝類 Float,Double 并沒有實作常量池技術,String也實作了常量池,比如:

public static void main(String[] args) {
   String a="123";
   String b="123";
   String c=new String("123");
   System.out.println(a==b); //true
   System.out.println(a.equals(c));//true
}

因為ab都是從常量池內取值,所以這倆個值相等,那ac不應該回傳true啊,因為這倆物件在堆中的參考地址一定不同啊,這個時候需要一個新的知識點,==equal的區別

基礎資料型別(Byte,Short,Integer,Long,Character,Boolean),== equal 都是作用于比較物件內容(堆)是否相同,

參考物件型別, == equal 都是作用于比較物件記憶體地址(堆疊)是否相同,

那既然是這樣ac更應該是false,所有的類都繼承Object類,如果不重寫equals(),默認執行的是Objectequals()方法

public boolean equals(Object obj) {
 return (this == obj);
}

String 類重寫了equals()方法,所以a,ctrue

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

對于基本資料型別,(Byte,Short,Integer,Long,Character,Boolean)這5種包裝類默認創建了數值[-128127]的相應型別的快取資料,但是超出此范圍仍然會去創建新的物件,而FloatDouble則沒有,所以創建物件后的參考地址必然不同,

public static void main(String[] args) {
   Integer a=300;
   Integer b=300;
   Integer c=30;
   Integer d=30;
    System.out.println(a==b);//false
    System.out.println(c==d);//true
}

總結:相同內容的物件地址不一定相同,但相同地址的物件內容一定相同,

二、類加載

類加載程序

當程式主動使用某個類時,如果該類還沒有被加載到記憶體,則JVM會通過加載、連接、初始化來對這個類進行初始化,

類加載生命周期

加載

加載,是指Java虛擬機查找位元組流(查找.class檔案),并且根據位元組流創建java.lang.Class物件的程序,這個程序,將類的.class檔案中的二進制資料讀入記憶體,放在運行時區域的方法區內,然后在堆中創建java.lang.Class物件,用來封裝類在方法區的資料結構,

類加載階段:

(1)Java虛擬機將.class檔案讀入記憶體,并為之創建一個Class物件,

(2)任何類被使用時系統都會為其創建一個且僅有一個Class物件,

(3)這個Class物件描述了這個類創建出來的物件的所有資訊,比如有哪些構造方法,都有哪些成員方法,都有哪些成員變數等,

驗證

驗證階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機的要求,并且不會危害虛擬機自身的安全,整體來看,驗證階段大致分為4個驗證動作,

1、檔案格式驗證

第一階段是驗證位元組流是否符合Class檔案格式的規范,并且能被當前版本的虛擬機處理,比如是否以魔數開頭,(為了方便虛擬機識別一個檔案是否是class型別的檔案,SUN公司規定每個class檔案都必須以一個word(四個位元組)作為開始,這個數字就是魔數,主、次版本號是否在當前虛擬機處理范圍內;常量池的常量資料型別是否被支持,

2、元資料驗證

元資料驗證是對位元組碼描述資訊進行語意分析,以保證其描述的資訊符合Java語言規范的要求,這個階段可能的驗證點:

a.是否有父類;

b.是否繼承了不被允許繼承的類;

c.如果該類不是抽象類,是否實作了其父類或介面要求實作的所有方法;

3、位元組碼驗證

位元組碼驗證的主要目的是通過資料流和控制流分析,確定程式語意的合法性和邏輯性,該階段將對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會做出危害虛擬機安全的事情,這個階段可能的驗證點:

a.保證任何時候運算元堆疊的資料型別與指令代碼序列的一致性;

b.跳轉指令不會跳轉到方法體以外的位元組碼指令上;

4、符號參考驗證

符號參考驗證的主要目的是保證決議動作能正常執行,如果無法通過符號參考驗證,則會拋出例外,這個階段可能的驗證點:

a.符號參考的類、欄位、方法的訪問性(public、private等)是否可被當前類訪問;

b.指定類是否存在符合方法的欄位描述符;

準備

為靜態變數分配記憶體,并將其初始化為默認值,

注意:

public static int value = 1;在準備階段的初始值是 0而不是1,而把value賦值的putstatic指令將在初始化階段才會被執行,

特殊情況:

public static final int value = 1;//此時準備value賦值為1,

決議

決議階段是虛擬機將常量池內的符號參考替換成直接參考的程序,直接參考是直接指向目標的指標,相對偏移量或是一個能間接定位到目標的句柄,

初始化

當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化,為初始化變數賦值,執行類構造器等,

創建物件

  • new關鍵字創建 Class a=new A();此方法會呼叫建構式,
  • 通過反射的物體類.newInstance(), Class.forName("com.xiaojie.entity.User") 全限定類名,此方法會呼叫無參建構式,
  • constructor.newInstance(); 此方法會呼叫建構式,
  • clone()克隆方法,此方法不會呼叫建構式,淺克隆是指拷貝物件時僅僅拷貝物件本身(包括物件中的基本變數),而不拷貝物件包含的參考指向的物件,深克隆不僅拷貝物件本身,而且拷貝物件包含的參考指向的所有物件,
  • 使用反序列化,此方法可以進行深克隆,也不會呼叫建構式,
package com.xiaojie.entity;

/**
 * @Description:
 * @author: xiaojie
 * @date: 2021.09.22
 */
public class User  implements  Cloneable{

    private Long id;
    private String name;
    private Integer age;

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    public Long getId() {
        return id;
    }

    public User(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
        System.out.println("我是有參建構式,,,,,,");
    }

    public User() {
        System.out.println("我是無參的建構式,,,,,");
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        //如果要進行深克隆,需要在此處重寫clone()方法,對參考型物件進行克隆
        return super.clone();
    }

}

public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, CloneNotSupportedException, IOException {
    //1、new 關鍵字
    User user=new User();//呼叫無參建構式
    //2、通過反射的物體類.newInstance(), Class.forName("com.xiaojie.entity.User") 全限定類名
    User user1 = User.class.newInstance();//呼叫無參建構式
    Class<?> aClass = Class.forName("com.xiaojie.entity.User");
    User user2 = (User) aClass.newInstance();//呼叫無參建構式
    //3、constructor.newInstance();
    Constructor<User> constructor = User.class.getConstructor();
    User user3 = constructor.newInstance(); //呼叫無參建構式
    Constructor<User> constructor1 =User.class.getConstructor(Long.class,String.class,Integer.class);
    User user5 = constructor1.newInstance(1L, "tom", 18);//呼叫有參建構式
    //4、使用clone方法 不會呼叫構造器
    User user4= (User) user5.clone();
    System.out.println(user4);//com.xiaojie.entity.User@f6f4d33
    System.out.println(user5);//com.xiaojie.entity.User@23fc625e 可見復制后的物件并不相等,但是物件的屬性值是一樣的,
    System.out.println(user5.getName()==user4.getName()); //true
    //淺克隆是指拷貝物件時僅僅拷貝物件本身(包括物件中的基本變數),而不拷貝物件包含的參考指向的物件,
    //深克隆不僅拷貝物件本身,而且拷貝物件包含的參考指向的所有物件,
    //5、使反序列化,反序列化可以進行深克隆  不會呼叫構造器
    ObjectInputStream in = new ObjectInputStream(new FileInputStream(""));
    User user6 = (User) in.readObject();
}

類加載器

類加載器負責加載所有的類,其為所有被載入記憶體中的類生成一個java.lang.Class實體物件,一旦一個類被加載到JVM中,同一個類就不會被再次載入了,

JVM預定義的有三種類加載器

根類加載器(Bootstrap ClassLoader):或者叫啟動類加載器,它用來加載 Java 的核心類,是用原生代碼來實作的,并不繼承自 java.lang.ClassLoader(負責加載$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實作,不是ClassLoader子類),由于引導類加載器涉及到虛擬機本地實作細節,開發者無法直接獲取到啟動類加載器的參考,所以不允許直接通過參考進行操作,

擴展類加載器Extension ClassLoader):它負責加載JRE的擴展目錄,lib/ext或者由java.ext.dirs系統屬性指定的目錄中的JAR包的類,由Java語言實作,父類加載器為null

系統類加載器(Application ClassLoader):被稱為系統(也稱為應用程式)類加載器,它負責在JVM啟動時加載來自Java命令的-classpath選項、java.class.path系統屬性,或者CLASSPATH換將變數所指定的JAR包和類路徑,程式可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器,如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作為父加載器,由Java語言實作,父類加載器為ExtClassLoader,

類加載器加載類的大致步驟

JVM的類加載機制主要有如下3

全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和參考其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入,

雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類,通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父加載器,依次遞回,如果父加載器可以完成類加載任務,就成功回傳;只有父加載器無法完成此加載任務時,才自己去加載,

快取機制:快取機制將會保證所有加載過的Class都會被快取,當程式中需要使用某個Class時,類加載器先從快取區中搜尋該Class,只有當快取區中不存在該Class物件時,系統才會讀取該類對應的二進制資料,并將其轉換成Class物件,存入緩沖區中,這就是為很么修改了Class后,必須重新啟動JVM,程式所做的修改才會生效的原因,

  1. 在加載之前會判斷快取區是否存在該類物件,如果存在則直接回傳相應的物件,
  2. 如果不存在,則判斷該類加載器是否有父類加載器,或者自己是一個父類加載器,根加載器,
  3. 如果有父類加載器,則委托父類加載器去加載(如果父類有父類依次遞回),如果父類加載器沒有找到該類,則自己去加載該類,加載成功回傳,加載失敗,拋出ClassNotFoundExcepton的例外,
  4. 如果是根類加載器則利用根類加載器加載對應的物件,加載成功回傳,加載失敗,拋出ClassNotFoundExcepton的例外,

雙親委派模式

雙親委派機制,其作業原理的是,如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞回,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務,就成功回傳,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式,這就是雙親委派模式,

雙親委派模式的好處:采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系,通過這種層級關可以避免類的重復加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,再一個是考慮到安全因素,java核心api中定義型別不會被隨意替換,假設通過網路傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發現這個名字的類,發現該類已被加載,并不會重新加載網路傳遞的過來的java.lang.Integer,而直接回傳已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改,

如何破壞雙親委派模式

雙親委派代碼

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 檢查該類是否已經加載過,如果加載過就直接回傳
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
//沒加載過,呼叫父類加載器去加載,遞回呼叫
                    c = parent.loadClass(name, false);
                } else {
//沒有父類就啟動啟動類去加載,這是個native方法
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
//如果沒有找到拋出例外
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
//如果還沒找到,則自己去加載該類
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

破壞雙親委派有兩種方式

1、自定義類加載器,重寫findClass();
package com.xiaojie.classloader;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

/**
 * @Description: 自定義類加載器
 * 使用場景
 * (1)加密:Java代碼可以輕易的被反編譯,如果你需要把自己的代碼進行加密以防止反編譯,
 * 可以先將編譯后的代碼用某種加密演算法加密,類加密后就不能再用Java的ClassLoader去加載類了,
 * 這時就需要自定義ClassLoader在加載類的時候先解密類,然后再加載,
 *
 * (2)從非標準的來源加載代碼:如果你的位元組碼是放在資料庫、甚至是在云端,
 * 就可以自定義類加載器,從指定的來源加載類,
 *
 * (3)以上兩種情況在實際中的綜合運用:比如你的應用需要通過網路來傳輸 Java 類的位元組碼,
 * 為了安全性,這些位元組碼經過了加密處理,這個時候你就需要自定義類加載器來從某個網路地址上讀取
 * 加密后的位元組代碼,接著進行解密和驗證,最后定義出在Java虛擬機中運行的類,
 * @author: xiaojie
 * @date: 2021.09.23
 */
public class MyClassLoader extends ClassLoader {
    public MyClassLoader() {

    }

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File("D:/People.class");
        try {
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二進制流位元組組成的檔案轉換為一個java.lang.Class
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    private byte[] getClassBytes(File file) throws Exception {
        // 這里要讀入.class的位元組,因此要使用位元組流
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);

        while (true) {
            int i = fc.read(by);
            if (i == 0 || i == -1)
                break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        MyClassLoader mcl = new MyClassLoader();
        Class<?> clazz = Class.forName("People", true, mcl);
        Object obj = clazz.newInstance();
        System.out.println(obj);
        System.out.println("使用的類加載器是:" + obj.getClass().getClassLoader());
    }
}
1、	
2、使用執行緒背景關系類加載器,典型案例如JDBC連接,通過應用程式類加載器加載,
ClassLoader loader = Thread.currentThread().getContextClassLoader();

三、垃圾回識訓制

什么是垃圾回收

垃圾回收(Garbage Collection,GC),顧名思義就是釋放垃圾占用的空間,防止記憶體泄露,有效的使用可以使用的記憶體,對記憶體堆中不可達的物件進行清除和回收,垃圾回收是自動進行回收的,不能人為控制,程式員唯一能做的就是通過呼叫System.gc() 方法來"建議"執行垃圾收集器,但其是否可以執行,什么時候執行卻都是不可知的,

MinorGC和MajorGC

新生代 GCMinor GC):指發生在新生代的垃圾收集動作,因為 Java 物件大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快,

老年代 GCMajor GC / Full GC):指發生在老年代的 GC,出現了 Major GC,經常會伴隨至少一次的 Minor GC(但非絕對的,在 ParallelScavenge 收集器的收集策略里就有直接進行 Major GC 的策略選擇程序) MajorGC 的速度一般會比 Minor GC 10倍以上,

垃圾判斷演算法

參考計數器法

參考計數法就是給物件中添加一個參考計數器,每當有一個地方參考它時,計數器值加1;當參考失效時,計數器值減1,任何時刻計數器值為0的物件就是不可能再被使用的,但是這種方法不能判斷物件相互參考的這種情況,

根搜索演算法

根搜索演算法的基本思路就是通過一系列名為”GC Roots”的物件作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為參考鏈(Reference Chain),當一個物件到GC Roots沒有任何參考鏈相連時,則證明此物件是不可達的,

GC ROOTS主要回收的區域

(1). 虛擬機堆疊(堆疊幀中的區域變數區,也叫做區域變數表)中參考的物件,

(2). 方法區中的類靜態屬性參考的物件,

(3). 方法區中常量參考的物件,

(4). 本地方法堆疊中JNI(Native方法)參考的物件,

垃圾回收演算法

標記-清除

標記-清除包含兩部分,標記和清除,一部分標記出可達的物件(有的人認為是標記不可達的物件),然后清除掉不可達的物件,

這種演算法的缺點是容易產生不連續的空間碎片,而且標記和清除的效率都不是很高,這種演算法適合老年代的物件回收,

復制演算法

記憶體會被分為兩部分From區和To區,每次只是使用from區,to區則空閑著,當from區記憶體不夠了,開始執行GC操作,這個時候,會把from區存活的物件拷貝到to區,然后直接把from區進行記憶體清理,

這種演算法的雖然避免了標記-清除碎片化的問題,但是如果回收物件較多較大需要花費更長的時間,而且總會有一部分空間是空閑的,浪費記憶體空間,這種演算法適用于新生代的物件 ,

標記-整理(標記-壓縮)

標記整理和標記清除演算法比較相同,也經過標記階段,然后把可達物件移動到一端,對不可達的物件進行洗掉,

這種演算法也解決了空間碎片化的問題,但是移動物件,需要修改物件的參考地址,而且標記,整理效率也不高,這種演算法適合老年代的物件回收,

分代演算法

這種演算法,根據物件的存活周期的不同將記憶體劃分成幾塊,新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集演算法,新生代物件朝生夕死,物件數量多,只要重點掃描這個區域,那么就可以大大提高垃圾收集的效率,另外老年代物件存盤久,無需經常掃描老年代,避免掃描導致的開銷,

新生代使用復制演算法,因為新生代中的物件一般都是朝生夕死的,存活物件的數量并不多,這樣使用復制演算法進行拷貝時效率比較高,jvm將堆記憶體劃分為新生代與老年代,又將新生代劃分為Eden與2塊Survivor Space,然后在Eden –>Survivor Space 以及From Survivor Space 與To Survivor Space 之間實行復制演算法,

堆空間中新生代和老年代的默認比例是1:2(可以通過引數 –XX:NewRatio)來設定,在新生代中Eden:From:To=8:1:1 (通過引數 –XX:SurvivorRatio )來設定,

復制演算法的程序

  1. 當Eden區滿的時候,會觸發第一次MinorGC,把還活著的物件拷貝到Survivor From區,這個時候存活的物件就1歲了,當eden區再次執行MinorGC,就會掃描Eden和From區,把存活的物件復制到To區,然后清空Eden和From區,
  2. 當Eden區再次滿了之后,再次觸發MinorGC,就會掃描Eden和To(新的From區)區,然后將存活的物件復制到From區(新的To區),然后清空Eden和To區,
  3. 這樣依次往復,在From和To區之間復制來復制去,每熬過一次MinorGC的物件就長大一歲,當物件年滿15歲之后,依然存活,則會進入老年代,
  4. 可以通過引數設定-XX:MaxTenuringThreshold=15 默認也是15次

注意:這種情況不考慮,破格直接進入老年代的情況,

老年代使用標記清除或者標記整理

老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須“標記-清除-壓縮”演算法進行回收,

新生代如何進入老年代

  1. 創建大物件直接進入老年代 -XX:PretenureSizeThreshold=1M 只對SerialParNew收集器管用,
  2. 新生代采用的是復制收集演算法,S0和S1始終只是用其中一塊記憶體區,當出現MinorGC后大部分物件仍然存活的話,就需要老年代進行空間分配擔保,把survior區無法容納的物件直接晉升到老年代,
  3. 長期存活的物件>15歲
  4. 當 Survivor 空間中相同年齡(比如10)所有物件的大小總和大于 Survivor 空間的一半,年齡大于或等于該年齡(10)的物件就可以直接進入老年代,而不需要達到MaxTenuringThreshold的分代年齡,

如何觸發FullGC

  1. System.gc()方法的呼叫(大多數的情況下都會進行fullGC,但不能百分之百保證),
  2. 當老年代沒有足夠空間存放物件時(認為達到92%這個數值僅供參考),會觸發一次FullGC,
  3. 空間分配擔保時,如果剩余空間不足以盛放新生代的物件,這時要進行一次FullGC
  4. 如果元空間區域的記憶體達到了所設定的閾值-XX:MetaspaceSize=,觸發FullGC,

記憶體溢位和記憶體泄漏

記憶體溢位(out of memory),是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;比如系統就分給你10M的空間,你要存放20M的東西,這樣就會導致記憶體溢位,

產生原因:

1.記憶體中加載的資料量過于龐大,如一次從資料庫取出過多資料;

2.集合類中有對物件的參考,使用完后未清空,使得JVM不能回收;

3.代碼中存在死回圈或回圈產生過多重復的物件物體;

4.使用的第三方軟體中的BUG;

5.啟動引數記憶體值設定的過小

記憶體溢位解決方式就是增大jvm的記憶體

-Xmx3550m -Xms3550m 設定最大記憶體和初始化記憶體,兩者盡量一致,避免每次垃圾回收完成后JVM重新分配記憶體,

記憶體泄露(memory leak),是指程式在申請記憶體后,無法釋放已申請的記憶體空間,比如一個物件占用了10M的空間,但是它使用完了,一直不釋放,如果一次記憶體泄漏可以容忍,但是有很多的記憶體泄漏,不管有多少的記憶體遲早會被占用光,而導致的后果就是,記憶體溢位,

產生原因:

記憶體泄露的本質原因是因為代碼問題

  1. 不使用的物件不能被垃圾回識訓制回收,
  2. 使用完的資源記得關閉,比如io,資料庫等close(),

四、垃圾收集器

垃圾回收器有Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1、ZGC(jdk11之后),按照新生代和老年代來分,負責新生代的主要是 Serial、ParNew、Parallel Scavenge,負責老年代回收的是Serial Old、Parallel Old、CMS,而G1回收器可以對整個堆進行垃圾回收,

Serial垃圾收集器

特點:單執行緒、簡單高效(與其他收集器的單執行緒相比),對于限定單個CPU的環境來說,Serial收集器由于沒有執行緒互動的開銷,專心做垃圾收集自然可以獲得最高的單執行緒手機效率,收集器進行垃圾回收時,必須暫停其他所有的作業執行緒,直到它結束(Stop The World),使用新生代復制演算法,

應用場景:適用于Client模式下的虛擬機,

運行示意圖

ParNew垃圾收集器

和Serial完全一致,除了在收集器使用多執行緒外,

特點:多執行緒、ParNew收集器默認開啟的收集執行緒數與CPU的數量相同,在CPU非常多的環境中,可以使用-XX:ParallelGCThreads引數來限制垃圾收集的執行緒數,

   和Serial收集器一樣存在Stop The World問題

應用場景ParNew收集器是許多運行在Server模式下的虛擬機中首選的新生代收集器,因為它是除了Serial收集器外,唯一一個能與CMS收集器配合作業的,

Parallel Scavenge 垃圾收集器

Parallel Scavenge收集器是一個更關注吞吐量的收集器,與parnew 類似,

特點:屬于新生代收集器也是采用復制演算法的收集器,又是并行的多執行緒收集器(與ParNew收集器類似),

該收集器的目標是達到一個可控制的吞吐量,還有一個值得關注的點是:GC自適應調節策略(與ParNew收集器最重要的一個區別)

GC自適應調節策略Parallel Scavenge收集器可設定-XX:+UseAdptiveSizePolicy引數,當開關打開時不需要手動指定新生代的大小(-Xmn)、EdenSurvivor區的比例(-XX:SurvivorRation)、晉升老年代的物件年齡(-XX:PretenureSizeThreshold)等,虛擬機會根據系統的運行狀況收集性能監控資訊,動態設定這些引數以提供最優的停頓時間和最高的吞吐量,這種調節方式稱為GC的自適應調節策略,

Parallel Scavenge收集器使用兩個引數控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停頓時間
  • XX:GCRatio 直接設定吞吐量的大小,

Serial Old垃圾收集器

Serial Old是Serial收集器的老年代版本,

特點:同樣是單執行緒收集器,采用標記-整理演算法,

應用場景:主要也是使用在Client模式下的虛擬機中,也可在Server模式下使用,

Parallel Old垃圾回收器

Parallel Scavenge收集器的老年代版本,

特點:多執行緒,采用標記-整理演算法,

應用場景:注重高吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old 收集器,

CMS收集器

注意:“標記”是指將存活的物件和要回收的物件都給標記出來,而“清除”是指清除掉將要回收的物件,

其中,初始標記、重新標記這兩個步驟仍然需要“Stop The World”,

初始標記只是標記一下GC Roots能直接關聯到的物件,速度很快,

并發標記階段 :并不會阻礙用戶執行緒正常執行任務,與用戶執行緒并發執行進行標記,

重新標記階段則是為了修正并發標記期間因用戶程式繼續動作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發標記的時間短,

并發清除:對標記的物件進行清除回收,

CMS收集器的缺點:

  • 對CPU資源非常敏感,
  • 無法處理浮動垃圾,可能出現Concurrent Model Failure失敗而導致另一次Full GC的產生,
  • 因為采用標記-清除演算法所以會存在空間碎片的問題,導致大物件無法分配空間,不得不提前觸發一次Full GC,

未完待續

性能調優,實戰線上問題排查

,,,,,

參考:

https://blog.csdn.net/qzqanzc/article/details/81008598

JVM記憶體模型_哦絕影-CSDN博客_記憶體模型

jvm之java類加載機制和類加載器(ClassLoader)的詳解_翻過一座座山-CSDN博客_類加載器

自定義類加載器 - twoheads - 博客園

https://www.cnblogs.com/chenpt/p/9803298.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/339306.html

標籤:其他

上一篇:深入理解TCP實作,TCP三次握手四次揮手全講明白了

下一篇:虛擬機搭建jupyter notebook服務

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more