很多人对JVM管理分配对象有一个错误的理解,认为对象都存储在堆上,其实Java对象分配流程如下:

java对象存储到数据库 java对象存放在堆还是栈_赋值

一  首先来看栈上分配,依赖于逃逸分析和标量替换(下面介绍),本质是java虚拟机提供的一项优化技术,基本思想是将线程私有对象打散分配在栈,这样做的好处是可以在函数结束后自行销毁对象,不需要GC的介入,有效避免垃圾回收带来的负面影响。缺点是栈空间小,无法分配大对象。

A-1 逃逸分析

【概念】是编译语言中的一种优化分析,而不是一种优化的手段。通过对象的作用范围的分析,为其他优化手段提供分析数据从而进行优化。

【分类】包括:

  1. 全局变量赋值逃逸:变量赋值给全局变量;
  2. 方法返回值逃逸:变量作为方法的返回值;
  3. 实例引用发生逃逸:变量作为方法的入参;
  4. 线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量;

目的: 判断对象的作用域是否超出方法体[即:判断对象是否逃逸出方法体]

java对象存储到数据库 java对象存放在堆还是栈_赋值_02

标量替换

【概念】通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间。

提标量替换,就得先了解 标量与聚合量:

不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一步分解的聚合量。

拿实例解释:

java对象存储到数据库 java对象存放在堆还是栈_赋值_03

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

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

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

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

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

【结论】栈上分配依赖于逃逸分析标量替换

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

 关闭标量替换

 

. TLAB 分配

线程本地分配缓存。这是一块线程专用的内存分配区域。TLAB占用eden区的空间。在TLAB启用的情况下(默认开启),JVM会为每一个线程分配一块TLAB区域。

堆是线程共用的,因此可能会有多个线程在堆上申请空间,而每一次的对象分配都必须线程同步,会使分配的效率下降。考虑到对象分配几乎是Java中最常用的操作,因此JVM使用了TLAB这样的线程专有区域来避免多线程冲突,提高对象分配的效率。

    【局限性】 TLAB空间一般不会太大(占用eden区),所以大对象无法进行TLAB分配,只能直接分配到堆上。

    【分配策略】

  一个100KB的TLAB区域,如果已经使用了80KB,当需要分配一个30KB的对象时,TLAB是如何分配的呢?

此时,虚拟机有两种选择:第一,废弃当前的TLAB(会浪费20KB的空间);第二,将这个30KB的对象直接分配到堆上,保留当前TLAB(当有小于20KB的对象请求TLAB分配时可以直接使用该TLAB区域)。

JVM选择的策略是:在虚拟机内部维护一个叫refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,反之,则会废弃当前TLAB,新建TLAB来分配新对象。

【默认情况下,TLAB和refill_waste都是会在运行时不断调整的,使系统的运行状态达到最优。】

JVM参数

参数

作用

备注

-XX:+UseTLAB

启用TLAB

默认启用

-XX:TLABRefillWasteFraction

设置允许空间浪费的比例

默认值:64,即:使用1/64的TLAB空间大小作为refill_waste值

-XX:-ResizeTLAB

禁止系统自动调整TLAB大小

 

-XX:TLABSize

指定TLAB大小

单位:B