当前位置: 首页 > 科技观察

Java对象实际上在栈上分配内存?

时间:2023-03-14 21:03:53 科技观察

1逃逸分析JVM中高级的优化技术,比如类继承关系分析,这种技术不是直接优化代码,而是一种为其他优化措施提供依据的分析技术。分析对象的动态范围。当一个对象被定义在一个方法中时,它可能会从该方法中逃逸出来,并被外部方法引用,例如将其作为参数传递给其他方法。实例变量,所以Java对象的逃逸度从低到高为:无逃逸=”方法逃逸=”线程逃逸如果可以确定一个对象不会在方法或线程外(即其他方法和线程)逃逸无法访问对象)或者转义程度较低(只转义方法不转义线程),则可以针对对象实例采取不同程度的优化方案。2优化方案2.1StackAllocations由于复杂等原因,这个优化在HotSpot中还没有做,但是其他一些虚拟机(比如ExcelsiorJET)使用了这个优化。JVM的GC模块会回收堆中不再使用的对象,但是下面的回收动作会标记过滤掉可回收的对象。回收和组织内存会消耗大量资源。如果确定一个对象不会从线程中逸出,最好让对象在栈上分配内存,对象占用的内存空间可以随着栈帧出栈而销毁。在一般的应用中,根本不会逃逸的本地对象和线程不会逃逸的对象占了很大的比例。如果可以使用栈分配,在方法结束时会自动销毁大量对象,GC系统的压力就会下降。很多。堆栈上的分配支持方法逃逸,但不支持线程逃逸。2.2标量替换(ScalarReplacement)2.2.1标量如果一段数据不能再分解成更小的数据,那么JVM中原有的数据类型(如int、long等数值类型和引用类型)就无法进一步分解.是一个标量。2.2.2聚合如果一条数据可以继续分解,则称为聚合(Aggregate)。例如,Java对象是一个集合。2.2.3标量替换一个Java对象被拆解,根据程序访问情况,将其使用的成员变量恢复为原来的类型进行访问。如果逃逸分析能够证明一个对象不会在方法外被访问,并且对象可以被分解,那么程序在实际执行的时候可能不会创建对象,而是直接创建方法使用的它的几个成员变量.对象拆分后:允许对象的成员变量在栈上进行分配、读写(栈上存储的数据很可能会被JVM分配到物理机的高速寄存器中存储),为后续优化创造条件2.2.4适用场景标量替换可以看作是栈上分配的一种特例,实现起来比较简单(不考虑对象完整结构的分配),但对逃逸度要求较高,确实不允许对象脱离方法的作用域。2.3同步消除(SynchronizationElimination)线程同步是一个比较耗时的过程。如果逃逸分析可以确定一个变量不会从线程中逃逸出来,也就是不会被其他线程访问到,那么这个变量的读写肯定不会有线程竞争。也可以安全地消除对该变量实施的同步措施。逃逸分析论文发表于1999年,但直到JDK6,HotSpot才开始初步支持逃逸分析,目前还不成熟,主要是逃逸分析的计算成本太高,无法保证性能收益会更高比它。消耗。要100%准确地确定对象是否会逃逸,需要进行一系列复杂的数据流敏感过程间分析,以确定程序的每个分支执行时对对象的影响。过程间分析,一种高压分析算法,是即时编译的弱点。试想一下,如果在逃逸分析完成后,发现几乎没有几个不逃逸的对象,那么这些运行时所花费的时间就被浪费掉了,所以现在的JVM只能使用不太准确的,但是相对少时间紧迫的算法。完整的分析。C和C++本身就支持在栈上分配(就是不用new),而Java在栈内存的灵活使用上确实属于弱势群体。在尚处于实验阶段的Valhalla项目中,设计了一个新的inline关键字来定义Java的内联类型,相当于C#的值类型。有了这个标识和约束,以后做逃逸分析就会容易很多。3代码验证3.1未经优化的代码publicinttest(intx){intxx=x+2;Pointp=newPoint(xx,42);returnp.getX();}3.2优化step1:内联构造函数和getX()方法publicinttest(intx){intxx=x+2;//在堆中分配P个对象Pointp=point_memory_alloc();//Point构造函数内联后p.x=xx;p.y=42;//Point::getX()内联后返回p。x;}优化步骤2:标量替换逃逸分析,发现Point对象实例在整个test()方法范围内不会进行任何程度的逃逸,因此可以用标量替换:替换其内部的x和y在test()方法中直接替换分解为局部变量,从而避免了Point对象实例的创建publicinttest(intx){intxx=x+2;intpx=xx;intpy=42returnpx;}step3:无效代码消除数据流分析,发现py的值不会对方法产生任何影响,那么就可以放心的剔除无效代码,得到最终的优化结果ult,如下图:publicinttest(intx){returnx+2;}观察测试结果,实现逃逸分析后的程序往往能在MicroBenchmarks中取得不错的成绩,但在实际应用中,尤其是大型程序中,发现执行逃逸分析可能会导致结果不稳定,或者分析过程耗时但无法有效区分因此很长一段时间,连服务端编译器都默认不开启逃逸分析(从JDK6Update23开始,在服务端编译器中Escapeanalysis一开始只是默认开启的。),甚至在某些版本(比如JDK6Update18)中完全禁用了这个优化。直到JDK7,这个优化才成为服务端编译器默认启用的一个选项。如果有必要或者确认对程序有利,可以使用参数:-XX:+DoEscapeAnalysis手动开启逃逸分析。启用后可以通过参数:-XX:+PrintEscapeAnalysis查看分析结果。支持逃逸分析后,用户可以使用以下参数:-XX:+EliminateAllocations开启标量替换+XX:+EliminateLocks开启同步消除-XX:+PrintEliminateAllocations勾选标量替换让我们一起期待开发逃逸分析这种JIT优化技术。参考《深入理解 Java 虚拟机》本文转载自微信公众号“JavaEdge”,可通过以下二维码关注。转载本文请联系JavaEdge公众号。