1. 堆与栈优缺点
栈:
- 访问速度快,仅次于寄存器;
- 但存储在栈中的数据大小与生命周期必须是确定的。
堆:
- 由于要在运行时动态分配内存,所以数据访问速度较慢;
- 但可以动态分配内存,生存周期也不需要事先告诉编译器,灵活。
由于Java堆区是GC的重点回收区域,所以GC极有可能会在大内存的使用和频繁进行垃圾回收构成上称为系统性能瓶颈。为解决这个问题,JVM的设计者开始考虑是否一定需要将对象实例存储到Java堆区内。
2. 逃逸
如果一个对象的指针被多个方法或线程引用,那我们可以称这个指针发生了逃逸。
指针逃逸示例:
public class G {
public static B b;
public void globalVariablePointerEscape () { //赋值给全局变量,发生逃逸
b = new B();
}
public B methodPointerEscape () { //方法返回值,发生逃逸
return new B();
}
public void instancePassPointerEscape () { //实例引用发生逃逸
methodPointerEscape().printClassName(this);
}
}
class B{
public void printClassName(G g) {
System.out.println(g.getClass().getName());
}
}
3. 逃逸分析对JVM优化
我们可以采用逃逸分析原理对JVM进行优化,即针对栈的重新分配方式。首先需要分析并且找到未逃逸的变量,将变量类的实例内存直接在栈里分配(无须进入堆),分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。通过这种优化方式,栈空间直接作为临时对象的存储介质,从而减少了临时对象在堆内的分配数量,减轻了堆内GC的压力。
实例代码:
public void my_method() {
V v = new V();
//use v
......
v = null;
}
在这个方法中创建的局部对象被赋给了v,但是没有返回,没有赋给全局变量等操作,因此这个对象是没有逃逸的,是可以运行时在栈上分配和销毁的对象。没有发生逃逸的对象,由于生命周期都在一个方法体内,因此它们可以在运行时在栈上分配和销毁。
JDK1.7开始支持对象的栈分配和逃逸分析机制。这样的机制除了能将堆分配对象变成栈分配对象以外,逃逸分析还有其他两个优化应用:
- 同步消除。线程同步的代价是相当高的,同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,这样就能大大提高并发性和性能。
- 矢量替代。逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话,就可以将对象的部分甚至全部都保存在CPU寄存器内,这样能大大加快访问速度。
网友评论