面试官:Java虚拟机的内存分为哪几个区域?

我(微笑着):程序计数器、虚拟机栈、本地方法栈、堆、方法区

面试官:对象一般存放在哪个区域?

我:堆。

面试官:对象都存放在堆中吗?

我:是的。

面试官:你了解过逃逸分析吗?

我(皱了皱眉):是内存溢出吗?

面试官:不是的。

我(挠了挠头):不是很了解。

面试官:今天的面试先到这,回去等消息吧!

然后就没有然后了,不甘心的我开始了查找相关资料。

逃逸分析

逃逸分析(Escape Analysis)是一种确定对象的引用动态范围的分析方法,说人话就是:分析在程序的哪些地方可以访问到对象的引用。

当一个对象在方法中被分配时,该对象的引用可能逃逸到其它执行线程中,或是返回到方法的调用者。

如果一个方法中分配一个对象并返回一个该对象的引用针,那么该对象可能被访问到的地方就无法确定,此时对象的引用就发生了“逃逸”。

如果对象的引用存储在静态变量或者其它数据结构中,因为静态变量是可以在当前方法之外访问到,此时对象的引用也发生了“逃逸”。

逃逸分析确定某个对象的引用可以被访问的所有地方,以及确定能否保证对象的引用的生命周期只在当前进程或线程中。

逃逸状态

对象的逃逸状态一般分为三种:全局逃逸、参数逃逸、没有逃逸。

全局逃逸(GlobalEscape)

对象的引用逃出了方法或者线程。比如:对象的引用赋值给了一个静态变量,或者存储在一个已经逃逸的对象中, 或者对象的引用作为方法的返回值给了调用方法。

比如饿汉的单例模式:

package one.more;

public final class GlobalEscape {

    // instance对象赋值给了一个静态变量,发生了全局逃逸
private static GlobalEscape instance = new GlobalEscape(); private GlobalEscape() {
} public static GlobalEscape getInstance() {
return instance;
}
}

参数逃逸(ArgEscape)

对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸。这个状态是通过分析被调用方法的字节码来确定的。

比如:

package one.more;

public class ArgEscape {

    class Rectangle {

        private int length;
private int width; public Rectangle(int length, int width) {
this.length = length;
this.width = width;
} public int getArea() {
return this.length * this.width;
}
} public int getArea(int length, int width) {
Rectangle rectangle = buildRectangle(length, width);
return rectangle.getArea();
} private Rectangle buildRectangle(int length, int width){
Rectangle rectangle = new Rectangle(length, width);
// rectangle对象发生了参数逃逸
return rectangle;
}
}

没有逃逸(NoEscape)

方法中的对象没有发生逃逸,这意味着可以不将该对象分配在堆上。

比如:

package one.more;

public class NoEscape {

    class Rectangle {

        private int length;
private int width; public Rectangle(int length, int width) {
this.length = length;
this.width = width;
} public int getArea() {
return this.length * this.width;
}
} public int getArea(int length, int width) {
// rectangle对象没有逃逸
Rectangle rectangle = new Rectangle(length, width);
return rectangle.getArea();
}
}

逃逸分析后的优化

如果一个对象没有发生逃逸,或者只有参数逃逸,就可能为这个对象采取不同程度的优化,比如:栈上分配、标量替换、同步消除。

栈上分配(Stack Allocations)

如果一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。

那么,对象就会随着方法的结束而自动销毁了,可以降低垃圾收集器运行的频率,垃圾收集的压力就会下降很多。

标量替换(Scalar Replacement)

标量(Scalar)是指一个无法再分解成更小的数据的数据。Java虚拟机中的基本数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java中的对象就是典型的聚合量。

如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为基本类型来访问,这个过程就称为标量替换

如果一个对象没有发生逃逸,可以进行标量替换,那么对象的成员变量就在栈上分配和读写,不需要分配到堆中。

标量替换可以视作栈上分配的一种特例,实现更简单,但对逃逸程度的要求更高,它不允许对象没有发生逃逸。

同步消除(Synchronization Elimination)

线程同步本身是一个相对耗时的过程,如果一个对象没有逃逸出线程,无法被其他线程访问,那么该对象的读写肯定就不会有竞争,对该对象实施的同步加锁操作也就可以安全地消除掉。

总结

说了这么多,可以发现对象并不是都在堆上分配内存的。因为通过逃逸分析后,可以对没有逃逸的对象进行标量替换。

另外,由于复杂度等原因,HotSpot中目前还不支持栈上分配的优化。

最后,谢谢你这么帅,还给我点赞关注

微信公众号:万猫学社

微信扫描二维码

关注后回复「电子书」

获取12本Java必读技术书籍

面试官:Java中对象都存放在堆中吗?你知道逃逸分析?的更多相关文章

  1. 【性能优化】面试官:Java中的对象都是在堆上分配的吗?

    写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...

  2. python中一切皆是对象,对象都是在堆上存放的,一切都是指针

    1 由于对象都是在堆上存放的,所以,返回值可以任意返回. 这样看来,闭包里面的外部函数的内部变量也是对象,所以,当返回的内部函数被调用时,这个外部函数的变量就没有被释放. 这样看来,返回时,不需要考虑 ...

  3. 求你了,别再说Java对象都是在堆内存上分配空间的了!

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  4. JVM知识(一) 求你了,别再说Java对象都是在堆内存上分配空间的了!

    求你了,别再说Java对象都是在堆内存上分配空间的了! https://baijiahao.baidu.com/s?id=1661296872935371634&wfr=spider& ...

  5. 别再说Java对象都是在堆内存上分配空间的了!

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  6. Java中对象、对象引用、堆、栈、值传递以及引用传递的详解

    Java中对象.对象引用.堆.栈.值传递以及引用传递的详解 1.对象和对象引用的差别: (1).对象: 万物皆对象.对象是类的实例. 在Java中new是用来在堆上创建对象用的. 一个对象能够被多个引 ...

  7. 面试官:如何在Integer类型的ArrayList中同时添加String、Character、Boolean等类型的数据? | Java反射高级应用

    原文链接:原文来自公众号:C you again,欢迎关注! 1.问题描述     "如何在Integer类型的ArrayList中同时添加String.Character.Boolean等 ...

  8. Java中的对象都是在堆上分配的吗?

    作者:LittleMagic https://www.jianshu.com/p/8377e09971b8 为了防止歧义,可以换个说法: Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定 ...

  9. 【Spring注解驱动开发】面试官:如何将Service注入到Servlet中?朋友又栽了!!

    写在前面 最近,一位读者出去面试前准备了很久,信心满满的去面试.没想到面试官的一个问题把他难住了.面试官的问题是这样的:如何使用Spring将Service注入到Servlet中呢?这位读者平时也是很 ...

随机推荐

  1. Ajax向服务器发起请求

    Ajax向服务器发起请求的三个步骤: 1:创建Ajax 2:打开Ajax,打开Ajax请求 3:向服务器发起请求:需要知道地址和是get请求还是post方法 向服务器发起请求的两个方法:open 和 ...

  2. Cell的重用原理

    iOS设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存.要解决该问题,需要重用UITableViewC ...

  3. Java线程--BlockingQueue使用

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871704.html Java线程--BlockingQueue使用 阻塞队列就是内容满了之 ...

  4. CABasicAnimation动画

    使用CABasicAnimation动画: CALayer *znzLayer; = [[CALayer alloc]init]; //创建不断该表CALayer的transform属性动画 CABa ...

  5. NFS共享存储服务 (如果厌倦了外面的生活,那就来我身边吧,帮我插秧)

    NFS共享存储服务     1.NFS概述 2.在服务器使用NFS发布共享资源 3.在客户机中访问NFS共享资源 1.NFS概述: NFS是一种基于TCP/IP传输的网络文件系统协议.通过使用NFS协 ...

  6. Solution -「CF 1370F2」The Hidden Pair (Hard Version)

    \(\mathcal{Description}\)   Link (hard) & Link (easy).   这是一道交互题.   给定一棵 \(n\) 个结点的树,其中有两个是特殊结点. ...

  7. HashTable源码学习

    一.介绍 1.HashMap和HashTable的区别 1.相同点 二者都实现了Map接口. 底层都是哈西表 2.不同点 Hashtable继承自Dictionary类,而HashMap继承自Abst ...

  8. Spring容器变化之SmartLifecycle,LifecycleProcesso接口详述

    Spring Boot run方法启动后相应的服务也随之启动,这个操作很妙.使用者都不用关心什么服务怎么启动,不管多少个服务怎么启动只要符合Spring Boot的启动规则都可以使用其run方法同一启 ...

  9. Visual Studio Code 配置C、C++ 文件debug调试环境

    目录 vscode C/C++ Extension Pack 插件安装 vscode windows 端 debug 配置 window MinGW 环境安装 windows 端 C.CPP 单文件 ...

  10. python-利用pymysql获取数据简单使用总结

    python-利用pymysql获取数据库中的数据 1.前言 pymysql是一个第三方的模块,使用前需要安装 2.安装 pip install pymysql 3.基本使用 import pymys ...