這都Java16了,Java7特性還沒(méi)整明白,嗎?

特性總覽

以下是 Java 7 中引入的部分新特性

  • java.lang

    • Java 7 多線程下自定義類加載器的優(yōu)化
  • Java 語(yǔ)言特性

    • 改進(jìn)的類型推斷;
    • 使用 try-with-resources 進(jìn)行自動(dòng)資源管理
    • switch 支持 String
    • catch 多個(gè)異常;
    • 數(shù)字格式增強(qiáng)(允許數(shù)字字面量下劃線分割);
    • 二進(jìn)制字面量;
    • 增強(qiáng)的文件系統(tǒng);
    • Fork/Join 框架;
  • Java 虛擬機(jī) (JVM)

    • 提供新的 G1 收集器;
    • 加強(qiáng)對(duì)動(dòng)態(tài)調(diào)用的支持;
    • 新增分層編譯支持;
    • 壓縮 Oops;
    • 其他優(yōu)化;
  • 其他;

多線程下自定義類加載器的優(yōu)化

在 Java 7 之前,某些情況下的自定義類加載器容易出現(xiàn)死鎖問(wèn)題。

// 類的繼承情況:
class A extends B
class C extends D

// 類加載器:
Custom Classloader CL1:
    直接加載類 A
    委托 CL2 加載類 B
Custom Classloader CL2:
    直接加載類 C
    委托 CL1 加載類 D

// 多線程下的情況:
Thread 1:
    使用 CL1 加載類 A
    → 定義類 A 的時(shí)候會(huì)觸發(fā) loadClass(B),這時(shí)會(huì)嘗試 鎖住?? CL2    
Thread 2:
    使用 CL2 加載類 C
    → 定義 C 的時(shí)候會(huì)觸發(fā) loadClass(D),這時(shí)會(huì)嘗試 鎖住?? CL1
?? 造成 死鎖??
復(fù)制代碼

造成死鎖的重要原因出在 JDK 默認(rèn)的 java.lang.ClassLoader.loadClass() 方法上:


可以看到,JDK 6 及之前的 loadClass() 的 synchronized 關(guān)鍵字是加在方法級(jí)別的,那么這就意味加載類時(shí)獲取到的是一個(gè) ClassLoader 級(jí)別的鎖。

我們來(lái)描述一下死鎖產(chǎn)生的情況:

文字版的描述如下:

  • 線程1:CL1 去 loadClass(A) 獲取到了 CL1 對(duì)象鎖,因?yàn)?A 繼承了類 B,defineClass(A) 會(huì)觸發(fā) loadClass(B),嘗試獲取 CL2 對(duì)象鎖;

  • 線程2:CL2 去 loadClass(C) 獲取到了 CL2 對(duì)象鎖,因?yàn)?C 繼承了類 D,defineClass(C) 會(huì)觸發(fā) loadClass(D),嘗試獲取 CL1 對(duì)象鎖

  • 線程1 嘗試獲取 CL2 對(duì)象鎖的時(shí)候,CL2 對(duì)象鎖已經(jīng)被 線程2 拿到了,那么 線程1 等待 線程2 釋放 CL2 對(duì)象鎖。

  • 線程2 嘗試獲取 CL1 對(duì)像鎖的時(shí)候,CL1 對(duì)像鎖已經(jīng)被 線程1 拿到了,那么 線程2 等待 線程1 釋放 CL1 對(duì)像鎖。

然后兩個(gè)線程一直在互相等中…從而產(chǎn)生了死鎖現(xiàn)象...

究其原因就是因?yàn)?ClassLoader 的鎖太粗粒度了。在 Java 7 中,在使用具有并行功能的類加載器的時(shí)候,將專門(mén)用一個(gè)帶有 類加載器和類名稱組合的對(duì)象 用于進(jìn)行同步操作。(感興趣可以看一下 loadClass() 內(nèi)部的 getClassLoadingLock(name) 方法)

Java 7 之后,之前線程死鎖的情況將不存在:

線程1:
  使用CL1加載類A(鎖定CL1 + A)
    defineClass A觸發(fā)
      loadClass B(鎖定CL2 + B)

線程2:
  使用CL2加載類C(鎖定CL2 + C)
    defineClass C觸發(fā)
      loadClass D(鎖定CL1 + D)

改進(jìn)的類型推斷

在 Java 7 之前,使用泛型時(shí),您必須為變量類型及其實(shí)際類型提供類型參數(shù):

Map<String, List<String>> map = new HashMap<String, List<String>>();

在 Java 7 之后,編譯器可以通過(guò)識(shí)別空白菱形推斷出在聲明在左側(cè)定義的類型:

Map<String, List<String>> map = new HashMap<>();

自動(dòng)資源管理

在 Java 7 之前,我們必須使用 finally 塊來(lái)清理資源,但防止系統(tǒng)崩壞的清理資源的操作并不是強(qiáng)制性的。在 Java 7 中,我們無(wú)需顯式的資源清理,它允許我們使用 try-with-resrouces 語(yǔ)句來(lái)借由 JVM 自動(dòng)完成清理工作。

Java 7 之前:

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader(path));
    return br.readLine();
} catch (Exception e) {
    log.error("BufferedReader Exception", e);
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (Exception e) {
            log.error("BufferedReader close Exception", e);
        }
    }
}

Java 7 及之后的寫(xiě)法:

try (BufferedReader br = new BufferedReader(new FileReader(path)) {
    return br.readLine();
} catch (Exception e) {
    log.error("BufferedReader Exception", e);
}

switch 支持 String

switch 在 Java 7 中能夠接受 String 類型的參數(shù),實(shí)例如下:

String s = ...
switch(s) {
case "condition1":
    processCondition1(s);
    break;
case "condition2":
    processCondition2(s);
    break;
default:
    processDefault(s);
    break;
} 

catch 多個(gè)異常

自Java 7開(kāi)始,catch 中可以一次性捕捉多個(gè)異常做統(tǒng)一處理。示例如下:

public void handle() {
    ExceptionThrower thrower = new ExceptionThrower();
    try {
        thrower.manyExceptions();
    } catch (ExceptionA | ExceptionB ab) {
        System.out.println(ab.getClass());
    } catch (ExceptionC c) {
        System.out.println(c.getClass());
    }
}

請(qǐng)注意:如果 catch 塊處理多個(gè)異常類型,則 catch 參數(shù)隱式為 final 類型,這意味著,您不能在 catch 塊中為其分配任何值。

數(shù)字格式增強(qiáng)

為了解決長(zhǎng)數(shù)字可讀性不好的問(wèn)題,在 Java 7 中支持了使用下劃線分割的數(shù)字表達(dá)形式:

/**
 * Supported in int
 * */
int improvedInt = 10_00_000;
/**
 * Supported in float
 * */
float improvedFloat = 10_00_000f;
/**
 * Supported in long
 * */
float improvedLong = 10_00_000l;
/**
 * Supported in double
 * */
float improvedDouble = 10_00_000; 

二進(jìn)制字面量

在 Java 7 中,您可以使用整型類型 (byte、short、int、long) 并加上前綴 0b (或 0B) 來(lái)創(chuàng)建二進(jìn)制字面量。這在 Java 7 之前,您只能使用八進(jìn)制值 (前綴為 0) 或十六進(jìn)制值 (前綴為 0x 或者 0X) 來(lái)創(chuàng)建:

int sameVarOne = 0b01010000101;
int sameVarTwo = 0B01_010_000_101;
byte byteVar = (byte) 0b01010000101;
short shortVar = (short) 0b01010000101  

增強(qiáng)的文件系統(tǒng)

Java 7 推出了全新的NIO 2.0 API以此改變針對(duì)文件管理的不便,使得在java.nio.file包下使用Path、Paths、Files、WatchService、FileSystem等常用類型可以很好的簡(jiǎn)化開(kāi)發(fā)人員對(duì)文件管理的編碼工作。

1 - Path 接口 和 Paths 類

Path接口的某些功能其實(shí)可以和java.io包下的File類等價(jià),當(dāng)然這些功能僅限于只讀操作。在實(shí)際開(kāi)發(fā)過(guò)程中,開(kāi)發(fā)人員可以聯(lián)用Path接口和Paths類,從而獲取文件的一系列上下文信息。

  • int getNameCount(): 獲取當(dāng)前文件節(jié)點(diǎn)數(shù)
  • Path getFileName(): 獲取當(dāng)前文件名稱
  • Path getRoot(): 獲取當(dāng)前文件根目錄
  • Path getParent(): 獲取當(dāng)前文件上級(jí)關(guān)聯(lián)目錄

聯(lián)用Path接口和Paths類型獲取文件信息:

Path path = Paths.get("G:/test/test.xml");
System.out.println("文件節(jié)點(diǎn)數(shù):" + path.getNameCount());
System.out.println("文件名稱:" + path.getFileName());
System.out.println("文件根目錄:" + path.getRoot());
System.out.println("文件上級(jí)關(guān)聯(lián)目錄:" + path.getParent());

2 - Files 類

聯(lián)用Path接口和Paths類可以很方便的訪問(wèn)到目標(biāo)文件的上下文信息。當(dāng)然這些操作全都是只讀的,如果開(kāi)發(fā)人員想對(duì)文件進(jìn)行其它非只讀操作,比如文件的創(chuàng)建、修改、刪除等操作,則可以使用Files類型進(jìn)行操作。

Files類型常用方法如下:

  • Path createFile(): 在指定的目標(biāo)目錄創(chuàng)建新文件
  • void delete(): 刪除指定目標(biāo)路徑的文件或文件夾
  • Path copy(): 將指定目標(biāo)路徑的文件拷貝到另一個(gè)文件中
  • Path move(): 將指定目標(biāo)路徑的文件轉(zhuǎn)移到其他路徑下,并刪除源文件

使用Files類型復(fù)制、粘貼文件示例:

Files.copy(Paths.get("/test/src.xml"), Paths.get("/test/target.xml"));

使用 Files 類型來(lái)管理文件,相對(duì)于傳統(tǒng)的 I/O 方式來(lái)說(shuō)更加方便和簡(jiǎn)單。因?yàn)榫唧w的操作實(shí)現(xiàn)將全部移交給 NIO 2.0 API,開(kāi)發(fā)人員則無(wú)需關(guān)注。

3 - WatchService

Java 7 還為開(kāi)發(fā)人員提供了一套全新的文件系統(tǒng)功能,那就是文件監(jiān)測(cè)。 在此或許有很多朋友并不知曉文件監(jiān)測(cè)有何意義及目,那么請(qǐng)大家回想下調(diào)試成熱發(fā)布功能后的 Web 容器。當(dāng)項(xiàng)目迭代后并重新部署時(shí),開(kāi)發(fā)人員無(wú)需對(duì)其進(jìn)行手動(dòng)重啟,因?yàn)?Web 容器一旦監(jiān)測(cè)到文件發(fā)生改變后,便會(huì)自動(dòng)去適應(yīng)這些“變化”并重新進(jìn)行內(nèi)部裝載。Web 容器的熱發(fā)布功能同樣也是基于文件監(jiān)測(cè)功能,所以不得不承認(rèn),文件監(jiān)測(cè)功能的出現(xiàn)對(duì)于 Java 文件系統(tǒng)來(lái)說(shuō)是具有重大意義的。
文件監(jiān)測(cè)是基于事件驅(qū)動(dòng)的,事件觸發(fā)是作為監(jiān)測(cè)的先決條件。開(kāi)發(fā)人員可以使用java.nio.file包下的StandardWatchEventKinds類型提供的3種字面常量來(lái)定義監(jiān)測(cè)事件類型,值得注意的是監(jiān)測(cè)事件需要和WatchService實(shí)例一起進(jìn)行注冊(cè)。

StandardWatchEventKinds類型提供的監(jiān)測(cè)事件:

  • ENTRY_CREATE:文件或文件夾新建事件;
  • ENTRY_DELETE:文件或文件夾刪除事件;
  • ENTRY_MODIFY:文件或文件夾粘貼事件;

使用WatchService類實(shí)現(xiàn)文件監(jiān)控完整示例:

public static void testWatch() {
    /* 監(jiān)控目標(biāo)路徑 */
    Path path = Paths.get("G:/");
    try {
        /* 創(chuàng)建文件監(jiān)控對(duì)象. */
        WatchService watchService = FileSystems.getDefault().newWatchService();

        /* 注冊(cè)文件監(jiān)控的所有事件類型. */
        path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);

        /* 循環(huán)監(jiān)測(cè)文件. */
        while (true) {
            WatchKey watchKey = watchService.take();

            /* 迭代觸發(fā)事件的所有文件 */
            for (WatchEvent<?> event : watchKey.pollEvents()) {
                System.out.println(event.context().toString() + " 事件類型:" + event.kind());
            }

            if (!watchKey.reset()) {
                return;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

通過(guò)上述程序示例我們可以看出,使用WatchService接口進(jìn)行文件監(jiān)控非常簡(jiǎn)單和方便。首先我們需要定義好目標(biāo)監(jiān)控路徑,然后調(diào)用FileSystems類型的newWatchService()方法創(chuàng)建WatchService對(duì)象。接下來(lái)我們還需使用Path接口的register()方法注冊(cè)WatchService實(shí)例及監(jiān)控事件。當(dāng)這些基礎(chǔ)作業(yè)層全部準(zhǔn)備好后,我們?cè)倬帉?xiě)外圍實(shí)時(shí)監(jiān)測(cè)循環(huán)。最后迭代WatchKey來(lái)獲取所有觸發(fā)監(jiān)控事件的文件即可。

Fork/ Join 框架

1 - 什么是 Fork/ Join 框架

Java 7 提供的一個(gè)用于并行執(zhí)行任務(wù)的框架,是一個(gè)把大任務(wù)分割成若干個(gè)小任務(wù),最終匯總每個(gè)小任務(wù)結(jié)果后得到大任務(wù)結(jié)果的框架。比如我們要計(jì)算 1 + 2 + .....+ 10000,就可以分割成 10 個(gè)子任務(wù),讓每個(gè)子任務(wù)分別對(duì) 1000 個(gè)數(shù)進(jìn)行運(yùn)算,最終匯總這 10 個(gè)子任務(wù)的結(jié)果。

Fork/Join 的運(yùn)行流程圖如下:


2 - 工作竊取算法

工作竊取 (work-stealing) 算法是指某個(gè)線程從其他隊(duì)列里竊取任務(wù)來(lái)執(zhí)行。核心思想是:自己的活干完了去看看別人有沒(méi)有沒(méi)有干完的活兒,如果有就拿過(guò)來(lái)幫他干。

工作竊取的運(yùn)行流程圖如下:



工作竊取算法的優(yōu)點(diǎn)是充分利用線程進(jìn)行并行計(jì)算,并減少了線程間的競(jìng)爭(zhēng),其缺點(diǎn)是在某些情況下還是存在競(jìng)爭(zhēng),比如雙端隊(duì)列里只有一個(gè)任務(wù)時(shí)。并且消耗了更多的系統(tǒng)資源,比如創(chuàng)建多個(gè)線程和多個(gè)雙端隊(duì)列。

3 - 簡(jiǎn)單示例

讓我們通過(guò)一個(gè)簡(jiǎn)單的需求來(lái)使用下Fork/Join框架,需求是:計(jì)算1 + 2 + 3 + 4的結(jié)果。

使用Fork/Join框架首先要考慮到的是如何分割任務(wù),如果我們希望每個(gè)子任務(wù)最多執(zhí)行兩個(gè)數(shù)的相加,那么我們?cè)O(shè)置分割的閾值是2,由于是4個(gè)數(shù)字相加,所以Fork/Join框架會(huì)把這個(gè)任務(wù)fork成兩個(gè)子任務(wù),子任務(wù)一負(fù)責(zé)計(jì)算1 + 2,子任務(wù)二負(fù)責(zé)計(jì)算3 + 4,然后再join兩個(gè)子任務(wù)的結(jié)果。

因?yàn)槭怯薪Y(jié)果的任務(wù),所以必須繼承RecursiveTask,實(shí)現(xiàn)代碼如下:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * CountTask.
 *
 * @author blinkfox on 2018-01-03.
 * @originalRef http://blinkfox.com/2018/11/12/hou-duan/java/java7-xin-te-xing-ji-shi-yong/#toc-heading-5
 */
public class CountTask extends RecursiveTask<Integer> {

    /** 閾值. */
    public static final int THRESHOLD = 2;

    /** 計(jì)算的開(kāi)始值. */
    private int start;

    /** 計(jì)算的結(jié)束值. */
    private int end;

    /**
     * 構(gòu)造方法.
     *
     * @param start 計(jì)算的開(kāi)始值
     * @param end 計(jì)算的結(jié)束值
     */
    public CountTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    /**
     * 執(zhí)行計(jì)算的方法.
     *
     * @return int型結(jié)果
     */
    @Override
    protected Integer compute() {
        int sum = 0;

        // 如果任務(wù)足夠小就計(jì)算任務(wù).
        if ((end - start) <= THRESHOLD) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果任務(wù)大于閾值,就分裂成兩個(gè)子任務(wù)來(lái)計(jì)算.
            int middle = (start + end) / 2;
            CountTask leftTask = new CountTask(start, middle);
            CountTask rightTask = new CountTask(middle + 1, end);

            // 等待子任務(wù)執(zhí)行完,并得到結(jié)果,再合并執(zhí)行結(jié)果.
            leftTask.fork();
            rightTask.fork();
            sum = leftTask.join() + rightTask.join();
        }
        return sum;
    }

    /**
     * main方法.
     *
     * @param args 數(shù)組參數(shù)
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool fkPool = new ForkJoinPool();
        CountTask task = new CountTask(1, 4);
        Future<Integer> result = fkPool.submit(task);
        System.out.println("result:" + result.get());
    }

}

虛擬機(jī)增強(qiáng)

1 - 提供新的 G1 收集器

Java 7 引入了一個(gè)被稱為 Garbage-First (G1) 的垃圾收集器。G1 是服務(wù)器式的垃圾收集器 (設(shè)計(jì)初衷是盡量縮短處理超大堆——大于 4GB——時(shí)產(chǎn)生的停頓),適用于具有大內(nèi)存多處理器的計(jì)算機(jī)。

與之前收集器不同的是 G1 沒(méi)有使用 Java 7 之前連續(xù)的內(nèi)存模型:



而是將整個(gè) 堆空間 劃分為了多個(gè)大小相等的獨(dú)立區(qū)域 (Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分 (可以不連續(xù)) Region的集合:


G1 完全可以預(yù)測(cè)停頓時(shí)間,并且可以為內(nèi)存密集型應(yīng)用程序提供更高的吞吐量。

2 - 加強(qiáng)對(duì)動(dòng)態(tài)調(diào)用的支持

Java 7 之前字節(jié)碼指令集中,四條方法調(diào)用指令 (invokevirtual、invokespeicial、invokestatic、invokeinterface) 的第一個(gè)參數(shù)都是 被調(diào)用方法的符號(hào)引用,但動(dòng)態(tài)類型的語(yǔ)言只有在 運(yùn)行期 才能確定接受的參數(shù)類型。這樣,在 Java 虛擬機(jī)上實(shí)現(xiàn)的動(dòng)態(tài)類型語(yǔ)言就不得不使用“曲線救國(guó)”的方式 (如編譯時(shí)留個(gè)占位符類型,運(yùn)行時(shí)動(dòng)態(tài)生成字節(jié)碼實(shí)現(xiàn)具體類型到占位符類型的適配) 來(lái)實(shí)現(xiàn),這樣勢(shì)必讓動(dòng)態(tài)類型語(yǔ)言實(shí)現(xiàn)的復(fù)雜度增加,也可能帶來(lái)額外的性能或者內(nèi)存開(kāi)銷。

為了從 JVM 底層解決這個(gè)問(wèn)題 (早在 1997 年出版的《Java 虛擬機(jī)規(guī)范》第一版中就規(guī)劃了這樣一個(gè)愿景:“在未來(lái),我們會(huì)對(duì) Java 虛擬機(jī)進(jìn)行適當(dāng)?shù)臄U(kuò)展,以便更好的支持其他語(yǔ)言運(yùn)行于 Java 虛擬機(jī)之上”), Java 7 新引入了 invokedynamic 指令以及 java.lang.invoke 包。

3 - 分層編譯

Java 7 中引入的 分層編譯 為服務(wù)器 VM 帶來(lái)了客戶端一般的啟動(dòng)速度。通常,服務(wù)器 VM 使用 解釋器 來(lái)收集有關(guān)「提供給 編譯器 的方法」的分析信息。在分層模式中,除了 解釋器 之外,客戶端編譯器 還用于生成方法的編譯版本,這些方法收集關(guān)于自身的分析信息。由于編譯后的代碼比 解釋器 要快得多,程序在分析階段執(zhí)行時(shí)會(huì)有更好的性能。在許多情況下,可以實(shí)現(xiàn)比客戶機(jī) VM 更快的啟動(dòng),因?yàn)榉?wù)器編譯器生成的最終代碼可能在應(yīng)用程序初始化的早期階段就已經(jīng)可用了。分層模式還可以獲得比常規(guī)服務(wù)器 VM 更好的峰值性能,因?yàn)楦斓姆治鲭A段允許更長(zhǎng)的分析周期,這可能產(chǎn)生更好的優(yōu)化。(ps: 官方文檔如是說(shuō)...)

支持 32 位和 64 位模式,以及壓縮 Oops。在 java 命令中使用 -XX:+TieredCompilation 標(biāo)志來(lái)啟用分層編譯。
(ps: 這在 Java 8 是默認(rèn)開(kāi)啟的)

4 - 壓縮 Oops (CompressOops)

HotSpot JVM 使用名為 oops 或 Ordinary Object Pointers 的數(shù)據(jù)結(jié)構(gòu)來(lái)表示對(duì)象。這些 oops 等同于本地C指針。 instanceOops 是一種特殊的 oop,表示 Java 中的對(duì)象實(shí)例。
在 32 位的系統(tǒng)中,對(duì)象頭指針占 4 字節(jié),只能引用 4 GB 的內(nèi)存,在 64 位系統(tǒng)中,對(duì)象頭指針占 8 字節(jié)。更大的指針尺寸帶來(lái)了問(wèn)題:

更容易 GC,因?yàn)檎加每臻g更大了;
降低了 CPU 緩存命中率,因?yàn)橐粭l cache line 中能存放的指針數(shù)變少了;

為了能夠保持 32 位的性能,oop 必須保留 32 位。那么,如何用 32 位 oop 來(lái)引用更大的堆內(nèi)存呢?答案是——壓縮指針 (CompressedOops)。JVM 被設(shè)計(jì)為硬件友好,對(duì)象都是按照 8 字節(jié)對(duì)齊填充的,這意味著使用指針時(shí)的偏移量只會(huì)是 8 的倍數(shù),而不會(huì)是下面中的 1-7,只會(huì)是 0 或者 8:

mem:  | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
        ^                               ^

這就允許了我們不再保留所有的引用,而是每隔 8 個(gè)字節(jié)保存一個(gè)引用:

mem:  | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
        ^                               ^
        |    ___________________________|
        |   |
heap: | 0 | 1 |

CompressedOops,可以讓跑在 64 位平臺(tái)下的 JVM,不需要因?yàn)楦鼘挼膶ぶ?,而付?Heap 容量損失的代價(jià) (其中還涉及零基壓縮優(yōu)化——Zero-Based Compressed OOPs 技術(shù))。 不過(guò)它的實(shí)現(xiàn)方式是在機(jī)器碼中植入壓縮與解壓指令,可能會(huì)給 JVM 增加額外的開(kāi)銷。

其他優(yōu)化

將 interned 字符串移出 perm gen

在 JDK 7 中,interned 字符串不再在 Java 堆的永久生成中分配,而是在 Java 堆的主要部分 (稱為年輕代和年老代) 中分配,與應(yīng)用程序創(chuàng)建的其他對(duì)象一起分配。這一更改將導(dǎo)致駐留在主 Java 堆中的數(shù)據(jù)更多,而駐留在永久生成中的數(shù)據(jù)更少,因此可能需要調(diào)整堆大小。由于這一變化,大多數(shù)應(yīng)用程序在堆使用方面只會(huì)看到相對(duì)較小的差異,但加載許多類或大量使用 String.intern() 方法的較大應(yīng)用程序?qū)⒖吹礁@著的差異。

(ps: String.intern() 方法是運(yùn)行期擴(kuò)展方法區(qū)常量池的一種手段)

NUMA 收集器增強(qiáng)

Java 7 對(duì) Parallel Scavenger 垃圾收集器進(jìn)行了擴(kuò)展,以利用具有 NUMA (非統(tǒng)一內(nèi)存訪問(wèn)) 體系結(jié)構(gòu)的計(jì)算機(jī)的優(yōu)勢(shì)。大多數(shù)現(xiàn)代計(jì)算機(jī)都基于 NUMA 架構(gòu),在這種架構(gòu)中,訪問(wèn)內(nèi)存的不同部分需要花費(fèi)不同的時(shí)間。通常,系統(tǒng)中的每個(gè)處理器都具有提供低訪問(wèn)延遲和高帶寬的本地內(nèi)存,以及訪問(wèn)速度相當(dāng)慢的遠(yuǎn)程內(nèi)存。
在 Java HotSpot 虛擬機(jī)中,已實(shí)現(xiàn)了 NUMA 感知的分配器,以利用此類系統(tǒng)并為 Java 應(yīng)用程序提供自動(dòng)內(nèi)存放置優(yōu)化。

分配器控制堆的年輕代的 eden 空間,在其中創(chuàng)建大多數(shù)新對(duì)象。

分配器將空間劃分為多個(gè)區(qū)域,每個(gè)區(qū)域都放置在特定節(jié)點(diǎn)的內(nèi)存中。

分配器基于以下假設(shè):分配對(duì)象的線程將最有可能使用該對(duì)象。

為了確保最快地訪問(wèn)新對(duì)象,分配器將其放置在分配線程本地的區(qū)域中。

可以動(dòng)態(tài)調(diào)整區(qū)域的大小,以反映在不同節(jié)點(diǎn)上運(yùn)行的應(yīng)用程序線程的分配率。

這甚至可以提高單線程應(yīng)用程序的性能。另外,年輕一代,老一代和永久一代的“從”和“到”幸存者空間為其打開(kāi)了頁(yè)面交錯(cuò)。這樣可以確保所有線程平均平均具有對(duì)這些空間的相等的訪問(wèn)延遲。

版本號(hào)大于 50 的類文件必須使用 typechecker 進(jìn)行驗(yàn)證

從 Java 6 開(kāi)始,Oracle 的編譯器使用 StackMapTable 制作類文件。基本思想是,編譯器可以顯式指定對(duì)象的類型,而不是讓運(yùn)行時(shí)執(zhí)行此操作。這樣可以在運(yùn)行時(shí)提供極小的加速,以換取編譯期間的一些額外時(shí)間和已編譯的類文件 (前面提到的 StackMapTable) 中的某些復(fù)雜性。

作為一項(xiàng)實(shí)驗(yàn)功能,Java 6 編譯器默認(rèn)未啟用它。 如果不存在 StackMapTable,則運(yùn)行時(shí)默認(rèn)會(huì)驗(yàn)證對(duì)象類型本身。

版本號(hào)為 51 的類文件 (也就是 Java 7 的類文件) 是使用類型檢查驗(yàn)證程序?qū)iT(mén)驗(yàn)證的,因此,方法在適當(dāng)時(shí)必須具有 StackMapTable 屬性。對(duì)于版本 50 的類文件,如果文件中的堆棧映射丟失或不正確,則 HotSpot JVM 將故障轉(zhuǎn)移到類型推斷驗(yàn)證程序。對(duì)于版本為 51 (JDK 7 默認(rèn)版本) 的類文件,不會(huì)發(fā)生此故障轉(zhuǎn)移行為。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容