涉及到的JEP:java
最主要的一點就是,讓Java適應現代硬件:在Java語言發佈之初,一次內存訪問和一次數字計算的消耗時間是差很少的,可是如今,一次內存訪問耗時大概是一次數值計算的200~1000倍。從語言設計上來講,也就是間接訪問帶來的經過指針獲取的須要操做的內存,對於總體性能影響很大。數組
Java是基於對象的語言,也就是說,Java是一種基於指針重間接引用的語言。這個基於指針的特性,給每一個對象帶來了惟一標識性。例如判斷兩個Object的==,其實判斷的是兩個對象的內存相對映射地址是否相同,儘管兩個對象的field徹底同樣,他們的內存地址也不一樣。同時這個特性也給對象帶來了多態性,易變性還有鎖的特性。可是,並非全部對象都須要這種特性。app
因爲指針與間接訪問帶來了性能瓶頸,Java準備對於不須要這種特性的對象移除這種特性。因而乎,Value type出現了。框架
Value type用於表示純數據集合。全部不須要的指針特性被移除。其實就是,Java的對象頭被移除了。ide
來看一個例子:函數
final class Point { final int x; final int y; }
這個在內存中的結構是:性能
對於Value type:測試
value class Point { int x; int y }
這個在內存中的結構是:優化
咱們再來對比下數組的存儲:spa
對於CommonObj[]
,只有引用是連續存儲的,實際的值:
對於Value types,數組存儲能夠扁平化,不用分散存儲,採起真正的:
這樣,JVM不用再跑到堆上分配內存來存儲這種對象,而是能夠直接在棧上面分配。這樣,Value types的表現,就和Java的原始類型int等就很像了。與原始類型不一樣的是,Value types能夠有方法和fileds。
同時咱們還但願能讓它做爲接口泛型。咱們但願能有更普遍的接口泛型,不管是對象,仍是Value types,仍是原始類型(剛纔已經說明了,利用原始類型性能更好),而不是封裝的原始類型。這就引出了,Valhalla的另外一個重要更新(針對泛型):Specialized Generics
從字面上理解,其實就是指泛型不止針對對象,也須要包含Value types,還有最重要的是原始類型例如int這些。
目前(JDK14以前的),泛型必須是一個對象類。針對原始類型,也必須使用原始類型的封裝類,例如Integer
之於int
。這就違反了以前說的減小對象封裝,使用原始類型。因此這個優化對於Value Types的實現也是必須的。
順便一提,目前JDK框架的Java源碼也有不少使用原始類型從而提升性能的地方,例如IntStream
涉及到的全部int的操做函數,傳參都是int,而不是Integer:
@FunctionalInterface public interface IntUnaryOperator { int applyAsInt(int operand); }
inline
:Inline Classes這個Inline Classes實際上就是一種Value Types的實現。
咱們首先回顧下,普通類對象的存儲結構:
public static void main(String args) { CommonObj a = new CommonObj(); }
這段代碼,會在棧上新建一個引用變量a, 在堆上面申請一塊內存用於存儲新建的CommonObj這個對象,對象包括,
因爲目前JDK 14 還沒發佈,咱們只能經過目前開發版的OpenJDK進行嚐鮮。能夠經過這裏下載全平臺的OpenJDK project Valhalla嚐鮮版:http://jdk.java.net/valhalla/
因爲目前還沒開發完,咱們只能經過字節碼去解讀與原始類的不一樣。
目前,inline class的限制是:
咱們來聲明一個相似於java.util.OptionalInt
的類:
public inline class OptionalInt { private boolean isPresent; private int v; private OptionalInt(int val) { v = val; isPresent = true; } public static OptionalInt empty() { // New semantics for inline classes return OptionalInt.default; } public static OptionalInt of(int val) { return new OptionalInt(val); } public int getAsInt() { if (!isPresent) throw new NoSuchElementException("No value present"); return v; } public boolean isPresent() { return isPresent; } public void ifPresent(IntConsumer consumer) { if (isPresent) consumer.accept(v); } public int orElse(int other) { return isPresent ? v : other; } @Override public String toString() { return isPresent ? String.format("OptionalInt[%s]", v) : "OptionalInt.empty"; } }
編譯後,咱們反編譯一下代碼,查看下,發現:
public final value class OptionalInt { private final boolean isPresent; private final int v;
class 變成 value class
修飾,同時,按照以前的約束,這裏多了final修飾符。同時,全部的field也多了final修飾。
而後是構造器部分:
public static OptionalInt empty(); Code: 0: defaultvalue #1 // class OptionalInt 3: areturn public static OptionalInt of(int); Code: 0: iload_0 1: invokestatic #11 // Method "<init>":(I)OptionalInt; 4: areturn private static OptionalInt OptionalInt(int); Code: 0: defaultvalue #1 // class OptionalInt 3: astore_1 4: iload_0 5: aload_1 6: swap 7: withfield #3 // Field v:I 10: astore_1 11: iconst_1 12: aload_1 13: swap 14: withfield #7 // Field isPresent:Z 17: astore_1 18: aload_1 19: areturn
咱們來看java.util.OptionalInt
的of
方法對應的字節碼:
public static OptionalInt of(int); Code: 0: new #5 // class OptionalInt 3: dup 4: iload_0 5: invokespecial #6 // Method "<init>":(I)V 8 setfield 9: areturn
咱們發現,對於inline class,沒有new
也沒有serfield
這兩個字節碼操做。而是用defaultvalue
和withfield
代替。由於字段都是final的,不必保留引用,因此用withfield
首先編寫測試代碼,下面的OptionalInt在兩次測試中,分別是剛剛自定義的Inline class,還有java.util.OptionalInt
public static void main(String[] args) { int MAX = 100_000_000; OptionalInt[] opts = new OptionalInt[MAX]; for (int i=0; i < MAX; i++) { opts[i] = OptionalInt.of(i); opts[++i] = OptionalInt.empty(); } long total = 0; for (int i=0; i < MAX; i++) { OptionalInt oi = opts[i]; total += oi.orElse(0); } try { Thread.sleep(60_000); } catch (Exception e) { e.printStackTrace(); } System.out.println("Total: "+ total); }
運用jmap命令查看:
jmap -histo:live
對於Inline class:
num #instances #bytes class name (module) ------------------------------------------------------- 1: 1 800000016 [OptionalInt; 2: 1687 97048 [B (java.base@14-internal) 3: 543 70448 java.lang.Class (java.base@14-internal) 4: 1619 51808 java.util.HashMap$Node (java.base@14-internal) 5: 452 44600 [Ljava.lang.Object; (java.base@14-internal) 6: 1603 38472 java.lang.String (java.base@14-internal) 7: 9 33632 [C (java.base@14-internal)
大概佔用了8*100_000_000這麼多字節的內存,剩下的16字節是數組頭,這也符合以前提到的Value Type的特性。
對於java.util.OptionalInt
:
num #instances #bytes class name (module) ------------------------------------------------------- 1: 50000001 1200000024 java.util.OptionalInt 2: 1 400000016 [Ljava.util.OptionalInt; 3: 1719 98600 [B 4: 540 65400 java.lang.Class 5: 1634 52288 java.util.HashMap$Node 6: 446 42840 [Ljava.lang.Object; 7: 1636 39264 java.lang.String
大概多了400MB的空間,而且多了50000000個對象。而且根據以前的描述,內存分配並非在一塊兒連續的,發生垃圾回收的時候,下降了掃描效率。
import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; @State(Scope.Thread) @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) public class MyBenchmark { @Benchmark public long timeInlineOptionalInt() { int MAX = 100_000_000; infoq.OptionalInt[] opts = new infoq . OptionalInt[MAX]; for (int i=0; i < MAX; i++) { opts[i] = OptionalInt.of(i); opts[++i] = OptionalInt.empty(); } long total = 0; for (int i=0; i < MAX; i++) { infoq.OptionalInt oi = opts[i]; total += oi.orElse(0); } return total; } @Benchmark public long timeJavaUtilOptionalInt() { int MAX = 100_000_000; java.util.OptionalInt[] opts = new java . util . OptionalInt[MAX]; for (int i=0; i < MAX; i++) { opts[i] = java.util.OptionalInt.of(i); opts[++i] = java.util.OptionalInt.empty(); } long total = 0; for (int i=0; i < MAX; i++) { java.util.OptionalInt oi = opts[i]; total += oi.orElse(0); } return total; } }
結果:
Benchmark Mode Cnt Score Error Units MyBenchmark.timeInlineOptionalInt thrpt 25 5.155 ± 0.057 ops/s MyBenchmark.timeJavaUtilOptionalInt thrpt 25 0.589 ± 0.029 ops/s
能夠看出,Inline class的效率,遠大於普通原始類。