堆是分配对象存储的唯一选择吗?

堆是分配对象存储的唯一选择吗

随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。----------《深入理解Java虚拟机》

     在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析( Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

     此外,基于 OpenJDK深度定制的 TaoBaoVM,其中创新的GCIH(GC invisible heap)技术实现off-heap,将生命周期较长的Java对象从heap中移至heap外,并且GC不能管理GCIH内部的Java对象,以此达到降低Gc的回收频率和提升GC的回收效率的目的。

逃逸分析手段

     如何将堆上的对象分配到栈,需要使用逃逸分析手段
     这是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。
     通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。


     逃逸分析的基本行为就是分析对象动态作用域:
         当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
         当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中。

图片替换文本

结论:开发中能用局部变量的,就不要使用在方法外定义。

逃逸分析:代码优化

    使用逃逸分析,编译器可以对代码做如下优化:

    栈上分配:将堆分配转换为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配
         JIT编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就无须进行垃圾回收了。
         常见的栈上分配的场景:在逃逸分析中,已经说明了。分别是给成员变量赋值、方法返回值、实例引用传递


    同步省略:如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
         线程同步的代价是相当高的,同步的后果是降低并发性和性能。
         在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有,那么J哩编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除


    分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中
         标量(Scalar)是指一个无法再分解成更小的数据的据。Java中的原始数据类型就是标量。
         相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。
         在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过J优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换

public static void main(String[] args){
	alloc();
}
private static void alloc(){
	Point point = new Point(1,2);
	System.out.println("point.x=" + point.x + "; point.y= " + point.y);
}
Class Point(){
	private int x;
	private int y;
}

以上代码经过标量替换后,就会变成:

private static void alloc(){
	int x = 1;
	int y = 2;
	System.out.println("point.x=" + point.x + "; point.y= " + point.y);

    可以看到,Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为旦不需要创建对象了,那么就不再需要分配堆内存了。

标量替换为栈上分配提供了很好的基础。

逃逸分析小结:逃逸分析并不成熟

     其根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

     一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的。那这个逃逸分析的过程就白白浪费掉了。

     虽然这项技术并不十分成熟,但是它也是即时编译器优化技术中一个十分重要的手段

     注意到有一些观点,认为通过逃逸分析,JⅥM会在栈上分配那些不会逃逸的对象,这在理论上是可行的,但是取决于JM设计者的选择。据了解,Oracle Hotspot JVM中并未这么做,这一点在逃逸分析相关的文档里已经说明,所以可以明确所有的对象实例都是创建在堆上。

     目前很多书籍还是基于JDK7以前的版本,JDK已经发生了很大变化, intern字符串的缓存和静态变量曾经都被分配在永久代上,而永久代已经被元数据区取代。但是intern字符串缓存和静态变量并不是被转移到元数据区,而是直接在堆上分配,所以这一点同样符合前面一点的结论:对象实例都是分配在堆上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值