019 Java对象的栈上分配问题

本文介绍了Java对象分配的流程,特别是栈上分配和逃逸分析技术。栈上分配是通过分析变量是否逃逸出函数作用域,若未逃逸,则可能将对象实例分配在栈上,从而提高性能并减少垃圾回收。逃逸分析用于确定对象的作用域,而标量替换则是将对象拆分为成员变量在栈上分配。通过示例和GC日志分析,展示了开启和关闭这些优化技术对性能的影响。
摘要由CSDN通过智能技术生成

学软件技术,读第一手资料,去官方网站:Java SE Specifications

若想看垃圾收集的简易版本的,参见:Java垃圾收集基础


《深入理解Java虚拟机》中原文:由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。


简单的说,逃逸分析指的是分析变量能不能逃出它的作用域。标量替换栈上分配都是基于逃逸分析去做的。

首先什么是标量,所谓的标量指的是不能进一步分解的量。像 Java 的基础数据类型(int、long等数值类型以及 reference 类型等)以及对象的地址引用都是标量,因为它们是没有办法继续分解的。与标量对应的是聚合量,聚合量指的是可以进一步分解的量,比如字符串就是一个聚合量,因为字符串是用字节数组实现的,可以分解。又比如我们自己定义的变量也都是聚合量。

那么什么是标量替换呢?根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变景来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,很大机会会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。


1. Java对象分配流程

    

2. 栈上分配

    2.1 本质:Java虚拟机提供的一项优化技术

    2.2 基本思想: 将线程私有的对象打散分配在栈上

    2.3 优点:

       2.3.1 可以在函数调用结束后自行销毁对象,不需要垃圾回收器的介入,有效避免垃圾回收带来的负面影响

       2.3.2 栈上分配速度快,提高系统性能

    2.4 局限性: 栈空间小,对于大对象无法实现栈上分配

    2.4 技术基础: 逃逸分析

        2.4.1 逃逸分析的目的: 判断对象的作用域是否超出函数体[即:判断是否逃逸出函数体]

//user的作用域超出了函数setUser的范围,是逃逸对象
//当函数结束调用时,不会自行销毁user
private User user;
public void setUser(){
    user = new User();
    user.setId(1);
    user.setName("blueStarWei");
}

//u只在函数内部生效,不是逃逸对象
//当函数调用结束,会自行销毁对象u
public void createUser(){
    User u = new User();
    u.setId(2);
    u.setName("JVM");
}

    2.5 栈上分配示例

package com.blueStarWei.templet;

public class AllotOnStack {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    private static void alloc() {
        User user = new User();
        user.setId(1);
        user.setName("blueStarWei");
    }
}

        2.5.1 上述代码调用了1亿次alloc(),如果是分配到堆上,大概需要1.5GB的堆空间,如果堆空间小于该值,必然会触发GC。

        2.5.2 使用如下参数运行,发现不会触发GC

-server -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations

       2.5.3 使用如下参数(任意一行)运行,会发现触大量GC

//不使用逃逸分析
-server -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations

//不使用标量替换
-server -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:-EliminateAllocations

    2.5.4 GC日志

[GC (Allocation Failure)  4095K->528K(15872K), 0.0025208 secs]
[GC (Allocation Failure)  4624K->552K(15872K), 0.0012518 secs]
[GC (Allocation Failure)  4648K->608K(15872K), 0.0009262 secs]
......(省略)
3718

        2.5.4.1 GC日志解析

参数

作用

备注

   GC

 用来区分是 Minor GC 还是 Full GC 的标志(Flag). 

这里的 GC 表明本次发生的是 Minor GC.

Allocation Failure
引起垃圾回收的原因. 本次GC是因为年轻代中没有任何合适的区域能够存放需要分配的数据结构而触发的.
4095K->528K
 在本次GC之前和之后的年轻代内存使用情况. 本次GC前,年轻代使用空间4095K, GC后年轻代使用空间为528K
(15872K)
 年轻代的总的大小
0.0025208 secs
 本次GC使用时间(单位:秒)

      2.5.5 JVM参数解析

参数作用备注
-server
使用server模式只有在server模式下,才可以弃用逃逸分析
-Xmx15m
设置最大堆空间为15m如果在堆上分配,必然触发大量GC
-Xms15m
设初始对空间为15m
-XX:+DoEscapeAnalysis
启用逃逸分析默认启用
-XX:-DoEscapeAnalysis
关闭逃逸分析
-XX:+PrintGC
打印GC日志
   -XX:-UseTLAB 关闭TLAB

 TLAB(Thread Local Allocation Buffer)

线程本地分配缓存区

-XX:+EliminateAllocations
启用标量替换,允许对象打散分配到栈上

默认启用

-XX:-EliminateAllocations
 关闭标量替换

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值