20201209——java記憶體分析

宮城詩發表於2020-12-10

概況

java記憶體分為程式計數器,堆,本地方法棧,虛擬機器方法棧,方法區

存放所有new的物件和陣列
可以被所有的執行緒共享,不會存放別的物件引用

存放基本變數型別(會包含這個基本型別的具體數值)
引用物件的變數(會存放這個引用在堆裡面的具體地址)

方法區

可以被所有的執行緒共享
包含了所有的class和static變數

瞭解類的載入過程

當程式主要使用某個類的時候,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟來對類進行初始化

類的載入load

將類的class檔案讀入記憶體,併為之建立一個java.lang.Class物件,此過程由類載入器完成

類的連結

將類的二進位制資料合併到JRE中

驗證

確保載入類資訊符合jvm規範,沒有安全方面的問題

準備

正式為變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中分配

解析

虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程

類的初始化

JVM負責對類進行初始化連線

執行類構造器<clinit>()方法的過程,類構造器方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼語句塊中的語句合併的產生的。

當初始化一個類的時候,如果發現一個父類還沒有被初始化,則需要先觸發父類的初始化

虛擬機器會保證一個類的clinit方法在多執行緒環境中正確加鎖和同步

載入過程程式碼

/**
 * @Classname TestClassLoad
 * @Description TODO
 * @Date 2020/12/9 14:45
 * @Created by mmz
 */
public class TestClassLoad {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
    }


}

class A{
    static {
        System.out.println("A類靜態程式碼塊初始化");
        m = 300;
    }
    static int m = 100;

    public A(){
        System.out.println("A類的無參構造初始化");
    }
}

類什麼會初始化

類的主動引用(一定會發生類的初始化)

當虛擬機器啟動後,先初始化main方法所在的類
new一個類的物件
呼叫類的靜態成員和靜態方法
使用java.lang.reflect包的方法對類進行反射呼叫
當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類

類的被動引用(不會發生類的初始化)

當訪問一個靜態域,只有真正宣告這個域的類才會被初始化
通過陣列定義類引用,不會觸發此類的初始化
引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池了)

/**
 * @Classname TestClassInit
 * @Description TODO
 * @Date 2020/12/9 15:00
 * @Created by mmz
 */
public class TestClassInit {
    static {
        System.out.println("main類被載入");
    }

    public static void main(String[] args) throws ClassNotFoundException {
//        // 主動引用
//        Son son = new Son();
//
//        // 反射 也是主動引用
//        Class.forName("Son");

//        // 不會產生類的引用的方法
//        System.out.println(Son.b);

//        Son[] array = new Son[10];

        System.out.println(Son.M);
    }
}

class Father{
    static int b = 2;
    static {
        System.out.println("父類被載入");
    }
}

class Son extends Father{
    static {
        System.out.println("子類被載入");
    }

    static int m = 300;
    static final int M = 1;
}

類載入器

類載入器的作用

將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區執行時資料結構,然後在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料訪問的入口

類快取

標準的javase類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間不,不過JVM垃圾回收機制可以回收這些Class物件

類載入器型別

引導類載入器 Bootstap ClassLoader

用C++編寫的,是JVM自帶的類載入器,負責java平臺核心庫,用來裝載核心類庫,該載入器無法直接獲取

就是在rt.jar包裡面的類

擴充類載入器 Extension ClassLoader

負責jre/lib/ext 目錄下的jar包或者-D java.ext.dirs目錄下的jar包裝入工作

系統類載入器 System ClassLoader

負責java -classpath 或者java.class.path所指目錄下面的類和jar包裝入工作,是最常用的載入器

/**
 * @Classname TestClassLoader
 * @Description TODO
 * @Date 2020/12/10 1:11
 * @Created by mmz
 */
public class TestClassLoader {
    public static void main(String[] args) throws ClassNotFoundException {
        // 系統類載入器
        ClassLoader classLoader =  ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);

        // 獲取擴充類載入器
        ClassLoader parent = classLoader.getParent();
        System.out.println(parent);

        // 獲取根類載入器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);


        // 測試當前的類是誰載入的
        ClassLoader testClassLoader = Class.forName("TestClassLoader").getClassLoader();
        System.out.println(testClassLoader);


        // 測試jdk內建的類是誰載入的
        testClassLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(testClassLoader);

        // 得到系統類載入器可以載入的路徑
        System.out.println(System.getProperty("java.class.path"));
    }
}

建立執行時類的物件

通過反射可以獲得執行時類的完整結構
Field,Method,Constructor,Superclass,Interface,Annotation

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @Classname TestInfoClass
 * @Description TODO
 * @Date 2020/12/10 1:21
 * @Created by mmz
 */
/*獲取類的資訊*/
public class TestInfoClass {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class user = Class.forName("User");

        User users = new User();
        // 獲得類的名字
        System.out.println(user.getName()); // 可以得到包名

        // 獲得類的簡單名字
        System.out.println(user.getSimpleName());

        // 獲得類的屬性
        Field[] fields = user.getFields(); // 只能找到public屬性
        for (Field field : fields) {
            System.out.println(field);
        }

        fields = user.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("-----");
        // 獲得指定的名字
        System.out.println(user.getDeclaredField("name"));

        // 獲得類的方法,以及父類的所有方法

        Method[] methods = user.getMethods();
        for (Method method : methods) {
            System.out.println("正常的"+method);
        }

        // 只獲得本類的方法
        methods = user.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("私有的"+method);
        }

        // 獲得指定方法
        System.out.println(user.getMethod("getName", null));

        System.out.println(user.getMethod("setName", String.class));

        // 獲得指定的構造器
        Constructor[] declaredConstructors = user.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

    }
}

有了class物件我們能做些什麼

建立類的物件,呼叫Class物件的newInstance方法
必須滿足兩點
1)類必須有一個無參的構造器
2)類的構造器的訪問許可權必須足夠

呼叫指定的方法

通過Class類的getMethod方法,獲取一個method物件,並設定此方法需要操作時的所需要的引數型別

之後使用object invoke(Object obj,Object[] args)進行呼叫,向方法中傳遞obj物件的引數資訊

setAccessible

Method和Field,Constructor物件都有setAccessible方法
setAccessible作用是啟動和禁用訪問安全檢查的開關
引數值為true,j取消ava語言訪問檢查

反射操作泛型

java採用泛型擦除的機制來引入泛型,java中的泛型僅僅是給編譯器和javac使用的,確保資料的安全性和免去強制型別轉換的問題,但是,一旦編譯完成,所有和泛型相關的型別全部擦除。

為了通過反射操作這些型別,java新增了幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的類

在這裡插入圖片描述

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * @Classname TestGetFanxing
 * @Description TODO
 * @Date 2020/12/10 2:03
 * @Created by mmz
 */
/*通過反射獲取泛型*/
public class TestGetFanxing {
    public void test01(Map<String,User> map, List<User> list){
        System.out.println("test01");
    }

    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = TestGetFanxing.class.getDeclaredMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);
            if(genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        method = TestGetFanxing.class.getDeclaredMethod("test02",null);
        Type genericReturnType = method.getGenericReturnType();
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

獲取註解資訊

getAnnotation

ORM

object relationship Mapping 物件關係對映

import java.lang.annotation.*;
import java.lang.reflect.Field;

/**
 * @Classname TestAnnotation
 * @Description TODO
 * @Date 2020/12/10 2:12
 * @Created by mmz
 */
/*反射操作註解*/
public class TestAnnotation {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("Student2");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 獲取註解value的值
        Mmz mmz  =(Mmz)c1.getDeclaredAnnotation(Mmz.class);
        String value = mmz.value();
        System.out.println(value);

        // 獲得類指定的註解
        Field name = c1.getDeclaredField("name");
        Fieldmmz annotation = name.getAnnotation(Fieldmmz.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.length());
        System.out.println(annotation.type());
    }

}
@Mmz("db_student")
class Student2{
    @Fieldmmz(columnName = "db_id",type = "int",length = 10)
    private int id ;
    @Fieldmmz(columnName = "db_age",type = "int",length = 10)
    private int age;
    @Fieldmmz(columnName = "db_name",type = "varchar",length = 3)
    private String name;

    public Student2() {
    }

    public Student2(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

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

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

// 類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Mmz{
    String value();
}

// 屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldmmz{
    String columnName();
    String type();
    int length();
}

相關文章