原创|面试官:Java对象一定分配在堆上吗?

原创|面试官:Java对象一定分配在堆上吗?

最近在看 Java 虚拟机方面的资料,以备工作中的不时之需。首先我先抛出一个我自己想的面试题,然后再引出后面要介绍的知识点如逃逸分析、标量替换、栈上分配等知识点

面试题

Java 对象一定分配在堆上吗?

自己先思考下,再往下阅读效果更佳哦!

分析

我们都知道 Java 对象一般分配在堆上,而堆空间又是所有线程共享的。了解 NIO 库的朋友应该知道还有一种是堆外内存也叫直接内存。直接内存是直接向操作系统申请的内存区域,访问直接内存的速度一般会优于堆内存。直接内存的大小不直接受 Xmx 设定的值限制,但是在使用的时候也要注意,毕竟系统内存有限,堆内存和直接内存的总和依然还是会受操作系统的内存限制的。

通过上面的分析,大家也知道了,Java 对象除了可以分配在堆上,还可以直接分配在堆外内存中。但这点不是我今天想讨论的,我想和大家聊聊栈上分配,说到栈上分配就不得不先说下逃逸分析

逃逸分析

逃逸分析是是一种动态确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。

换句话说,逃逸分析的目的是判断对象的作用域是否有可能逃出方法体

判断依据有两个

  1. 对象是否被存入堆中(静态字段或堆中对象的实例字段)
  2. 对象是否被传入未知代码中(方法的调用者和参数)

我们来分析下这两个依据

对于第一点对象是否被存入堆中,我们知道堆内存是线程共享的,一旦对象被分配在堆中,那所有线程都可以访问到该对象,这样即时编译器就追踪不到所有使用到该对象的地方了,这样的对象就属于逃逸对象,如下所示

public class Escape {
    private static User u;
    public static void alloc() {
        u = new User(1, "baiya");
    }
}

User 对象属于类 Escape 的成员变量,该对象是可能被所有线程访问的,所以会发生逃逸

第二点是对象是否被传入未知代码中,Java 的即时编译器是以方法为单位进行编译,即时编译器会把方法中未被内联的方法当成未知代码,所以无法判断这个未知方法的方法调用会不会将调用者或参数放到堆中,所以认为方法的调用者和参数是逃逸的,如下所示

public class Escape {
    private static User u; 
    public static void alloc(User user) {
        u = user;
    }
}

方法 alloc 的参数 user 被赋值给类 Escape 的成员变量 u,所以也会被所有线程访问,也是会发生逃逸的。

栈上分配

栈上分配是 Java 虚拟机提供的一种优化技术,该技术的基本思想是可以将线程私有的对象打散,分配到栈上,而非堆上。那分配到栈上有什么好处呢?
我们知道栈中的变量会在方法调用结束后自动销毁,所以省掉了 jvm 进行垃圾回收,进而可以提高系统的性能

栈上分配是要基于逃逸分析标量替换实现的

我们通过一个具体的例子来验证下非逃逸分析的对象确实是分配到了栈上

public class OnStack {
    public static void alloc() {
        User user = new User(1, "baiya");
    }
    public static void main(String[] args) {
        long start = Instant.now().toEpochMilli();
        for (int i = 0; i < 100_000_000; i++) {
            alloc();
        }
        long end = Instant.now().toEpochMilli();
        System.out.println("耗时:" + (end - start));
    }
}

上面的代码是循环 1 亿次执行 alloc 方法创建 User 对象,每个 User 对象占用约 16 bytes(怎么计算的下面会说) 空间,创建 1 亿次,所以如果 User 都是在堆上分配的话则需要 1.5G 的内存空间。如果我们设置堆空间小于这个数,应该会发生 gc,如果设置的特别小,应该会发生大量的 gc。

我们用下面的参数执行上述代码

-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+EliminateAllocations

其中 -server 是开启 server 模式,逃逸分析需要 server 模式的支持

-Xmx10 -Xms10m,设置堆内存是 10m,远小于 1.5G

-XX:+DoEscapeAnalysis 开启逃逸分析

-XX:+PrintGCDetails 如果发生 gc,打印 gc 日志

-XX:+EliminateAllocations 开启标量替换,允许把对象打散分配在栈上,比如 User 对象,它有两个属性 id 和 name,可以把他们看成独立的局部变量分别进行分配

配置好 jvm 参数后,执行代码,查看结果可知执行了 3 次 gc,耗时 10 毫秒,可以推断出 User 对象并未全部分配到堆上,而是把绝大多数分配到了栈上,分配在栈上的好处是方法结束后自动释放对应的内存,是一种优化手段。

栈上分配

我们上面说了栈上分配依赖逃逸分析和标量替换,那么我们可以破坏其中任意一个条件,去掉逃逸分析就可以通过 -XX:-DoEscapteAnalysis 或者关闭标量替换 -XX:-EliminateAllocations 再去执行上述代码,观察执行情况,发现发生了大量的 gc,并且耗时 3182 毫秒,执行时间远远高于上面的 10 毫秒,所以可以推测出并未执行栈上分配的优化手段

堆上分配

计算 User 对象占用空间大小

对象由四部分构成

  1. 对象头:记录一个对象的实例名字、ID和实例状态。

    普通对象占用 8 bytes,数组占用 12 bytes (8 bytes 的普通对象头 + 4 bytes 的数组长度)

  2. 基本类型

    boolean,byte 占用 1 byte

    char,short 占用 2 bytes

    int,float 占用 4 bytes

    long,double 占用 8 bytes

  3. 引用类型:每个引用类型占用 4 bytes

  4. 填充物:以 8 的倍数计算,不足 8 的倍数会自动补齐

我们上面的 User 对象有两个属性,一个 int 类型的 id 占用 4 bytes,一个引用类型的 name 占用 4bytes,在加上 8 bytes 的对象头,正好是 16 bytes

总结

关于虚拟机的知识点还有很多而且也比较重要,如果懂对写优质代码、优化性能、排查问题等都是锦上添花,比如逃逸分析,即时编译器会根据逃逸分析的结果进行优化,如所消除以及标量替换。感兴趣的朋友可以自己查查资料学习下。通过这个栈上分配的例子,以后我们写代码时,把可以不逃逸的对象写进方法体中,这样就会被编译器优化,提升性能。而且也知道了上面面试题的答案,就是 Java 中的对象并一定分配在堆上,也可能分配在栈上

参考资料

  1. 《实战Java虚拟机》
  2. 《深入理解Java虚拟机》
  3. https://zh.wikipedia.org/wiki/%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90

欢迎关注公众号 【每天晒白牙】,获取最新文章,我们一起交流,共同进步!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容