涉及到的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的效率,远大于普通原始类。