周二早上好!今天也有java岗位上线,记得看看新机会哦~
昨天一位朋友在面试中说,所有对象都在堆中创建,被面试官一阵嘲笑。
作为一个合格java开发者都知道,基本上所有对象都是在堆上创建。别忘了,是基本上所有,这就涉及到了今天的话题:逃逸分析。
开始正文。
逃逸分析(Escape Analysis)是什么?
——通俗地讲,逃逸分析就是确定一个变量要放堆上还是栈上,是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
为什么需要逃逸?
——我们来看看如果变量都分配到堆上了会出现什么结果:
1)垃圾回收(GC)的压力不断增大
2)申请、分配、回收内存的系统开销增大(相对于栈)
3)动态分配产生一定量的内存碎片
逃逸分析的基本原理是什么?
——分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。
Freemen app——IT程序员的求职招聘平台,找IT工作,就来Freemen!
开启逃逸分析,编译器可以对代码进行如下优化:
JVM中通过如下参数可以指定是否开启逃逸分析:
-XX:+DoEscapeAnalysis :表示开启逃逸分析(JDK 1.7之后默认开启)。
-XX:-DoEscapeAnalysis :表示关闭逃逸分析。
线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以安全地消除掉。
如以下代码:
public void method() {
Object o = new Object();
synchronized (o) {
System.out.println(o);
}
}
对对象o加锁,但是对象o的生命周期与方法method()一样,所以不会被其他线程访问到,不会发生线程安全问题,那么在JIT编译阶段会被优化为如下所示:
public void method() {
Object o = new Object();
System.out.println(o);
}
这也被称为锁消除。
在Java虚拟机中,Java堆上分配创建对象的内存空间几乎是Java程序员都知道的常识,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。虚拟机的垃圾收集子系统会回收堆中不再使用的对象,但回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。但是,存在一种特殊情况,如果逃逸分析确认对象不会逃逸出线程之外,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。
如以下代码:
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
alloc();
}
Thread.sleep(100000);
}
private static void alloc() {
User user = new User();
}
代码很简单,就是循环创建100万次,使用alloc()方法创建100万个User对象。这里的alloc()方法中定义了User对象并没有被其他方法引用,所以符合栈上分配的要求。
JVM参数如下:
-Xmx2G -Xms2G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
启动程序,通过jmap工具查看实例数:
jmap -histo pid
num #instances #bytes class name
----------------------------------------------
1: 3771 2198552 [B
2: 10617 1722664 [C
3: 104057 1664912 com.miracle.current.lock.StackAllocationTest$User
我们可以看到程序总共创建了104057个User对象,远小于100万。我们可以关闭逃逸分析再来看下:
-Xmx2G -Xms2G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
启动程序,通过jmap工具查看实例数:
jmap -histo 42928
num #instances #bytes class name
----------------------------------------------
1: 628 22299176 [I
2: 1000000 16000000 com.miracle.current.lock.StackAllocationTest$User
可以看到,关闭逃逸分析后总共创建了100万个User对象。对比来看,栈上分配对堆内存消耗,GC都有着重要的作用。
若一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int 、long 等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java中的对象就是典型的聚合量。
假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么程序真正执行的时候将可能不去创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来代替。
有如下代码:
public static void main(String[] args) {
method();
}
private static void method() {
User user = new User(25);
System.out.println(user.age);
}
private static class User {
private int age;
public User(int age) {
this.age = age;
}
}
在method()方法中创建User对象,指定age为25,这里User不会被其他方法引用,也就是说它不会逃逸出方法,并且User是可以拆解为标量的。所以alloc()代码会优化为如下:
private static void alloc() {
int age = 25;
System.out.println(age);
}
尽管目前逃逸分析技术仍在发展之中,未完全成熟,但它是即时编译器优化技术的一个重要前进方向,在日后的Java虚拟机中,逃逸分析技术肯定会支撑起一系列更实用、有效的优化技术。
文章来源于Java后端技术全栈,作者田哥
今日推荐:
JAVA开发工程师
任职要求:
1、本科及以上学历,计算机或相关专业,3年以上Java开发经验,负责过软件项目的设计与开发;
2、熟练掌握基于Web的应用程序开发,理解SprinMVC、SpringBoot,SpringCloud,MyBaits等主流框架开发过程,掌握缓存机制(Redis、Memcached)和MQ消息机制;
3、精通SQL语言、熟悉Mysql或Oracle数据库,具有丰富的数据库开发经验,有SQL性能调优经验者优先;
4、 熟悉分布式、多线程、高并发及高可用的设计、编码和调优;熟悉大流量、高并发、高性能的分布式系统的设计及应用,擅长性能调优者优先;
5、乐于学习,积极分享,有Github开源项目优先。
语言要求:英语 - 公共英语四级
薪资:15k-30k
工作地点:北京
页面更新:2024-05-01
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号