Java8 中不起眼的新特性
Metaspace
一直以来,JVM 都在heap 中管理这一个称为PermGen的区域,用于放置Class对象实例, 静态变量, String pool等。PermGen的存在对于GC 有很大的影响,存在于PermGen 中的对象很少能被回收,一旦PermGen 满了就会触发Stop-the-world 的Full GC——即使是设置使用了CMS 等延迟更低的垃圾收集器也是如此。一旦垃圾回收也不能会受到足够的内存,OOM Error就会被引发,终止程序的执行。使用了动态字节码生产技术的时候(asm, 动态代理, groovy等),由于需要生产很多动态类,这种情况更为严重。
在Java7 中, String pool 被移出了PermGen。到了Java8,PermGen 被彻底取消,Class 对象等元数据被放到了新的空间Metaspace 中。Metaspace 不存在于JVM Heap中,不受GC 管辖,也降低了GC 的压力。由于只存储class 元数据,所以数据的移除策略也很简单,当没有这个class 的实例存在后,就可以移除。
64位的server JVM 中,Metaspace 初始大小为21M。当Metaspace 满了之后,依然会触发GC,同时会调大Metaspace 空间。如果必要的话,可以通过–XX:MetaspaceSize 来设置初始的大小,避免引发GC。
Stamped Locks
时间戳锁。时间戳锁是一种乐观锁,它维持一个序号,每个更新操作都会将将序号增加。在起始的时候,记录一个序号,在访问完成之后,如果这个序号没有改变,就认为这段时间没有访问冲突。这是一种性能极高的加锁方式。
long stamp = lock.tryOptimisticRead(); work; if (lock.validate(stamp)){ //success! no contention with a writer thread } else { // 访问冲突, 直接fail back 到普通的锁 stamp = lock.readLock(); try { //no writing happening now work(); } finally { lock.unlock(stamp); } }
Concurrent Adders 是Atomic类的进化版本。原子类通过不断的cas 自旋,来实现原子的更新。如果竞争激烈的情况下,会一直进行cas 操作直到成功,消耗CPU 资源。
Concurrent Adders 为每个线程保存一个本地的增量。在cas 自旋失败之后,直接将要add 的增量写到线程本地的增量中。当要读取的时候,在取出数据的基础上,再加上本线程以及其他线程的增量,从而得到一个最终正确的数值。
除非有特殊原因,使用Concurrent Adders 代替原子类是一个很好的选择。
Parallel Sorting
增加了充分利用多核的排序方法:
String joined = new StringJoiner("-") .add("foo") .add("bar") .add("baz") .toString(); // "foo-bar-baz"
这个是从scala 等函数式编程语言借鉴来的东西,在函数式编程语言中,Optional 用来消除null 引起的副作用,是一种Monad 的方式。Guava 等lib 已经有了类似的工具,现在Java 把它引入到了标准库中。Optional 是一个泛型类,它可以hold一个null 或者非null 的value,提供get(), orElse(), ifPresent() 等方法。
Optional<User> user = tryFindUser(int userID); user.ifPresent(System.out::print);