JVM之栈上分配

java对象分配流程

我们都知道一般Java对象实例的创建都在堆上,GC对堆进行垃圾回收,但并不是所有的Java对象创建都在堆上,Java虚拟机栈也可以创建对象,我们先看下Java对象创建的时候内存分配的流程图:
对象分配流程图
可以看到,在创建对象的时候首先是判断能不能在栈上分配,然后再是堆上(TLAB也属于堆内存),所以并不是所有的Java对象都在堆上创建的。

栈上分配

对象可以在栈上分配要满足三个条件:逃逸分析标量替换方法内联

为什么在栈上分配内存

在栈上创建Java对象可以减少堆内存的压力,栈上创建的对象会随着方法的出栈销毁,这样也避免了GC回收。其次在栈上创建的对象可以被方法直接访问,不需要再持有对象的引用,然后再找到堆上的数据,对象的访问速度也会变快。栈上分配的对象不需要做同步操作,因为栈上分配的对象是私有的,其他线程是不可见的,可以进行锁消除。

逃逸分析

栈上分配的首要条件就是逃逸分析,逃逸分析是JVM分析一个对象的动态作用域,判断对象会不会逃逸出方法,例如作为参数传递到其他方法中,或者被外部使用,这样的行为就是对象逃逸了。为什么栈上分配需要对象没有逃逸出方法呢?因为一般的Java对象都是在堆上分配的,堆上分配的对象是其他方法或者线程共享的,一旦创建了,其他方法也可以持有这个对象的引用,但是如果一个对象在栈上分配(其实是在栈帧上分配的),其他方法或者线程是没法访问的,也就是不可见的。所以栈上分配的对象要求没有进行逃逸。

标量替换

因为Java虚拟机栈的数据都是一个一个的栈帧,而栈帧里面存放的是局部变量、动态链接、操作数栈、方法出口地址,并没有一个区域可以存放对象实例的数据,这个时候就需要标量替换了。标量替换,scalar replacement。Java中的原始类型无法再分解,可以看作标量(scalar);指向对象的引用也是标量;而对象本身则是聚合量(aggregate),可以包含任意个数的标量。如果把一个Java对象拆散,将其成员变量恢复为分散的变量,这就叫做标量替换。拆散后的变量便可以被单独分析与优化,可以各自分别在活动记录(栈帧或寄存器)上分配空间;原本的对象就无需整体分配空间了。举例:

1
2
3
4
class User {
public int id;
public int age;
}

User对象就可以进行标量替换,

1
User user = new User();

1
2
int id;
long age;

给变量赋值进行替换:

1
user.id = 10;

1
id = 10;

所以对象进行标量替换后就解决了对象在栈帧上存储的问题,替换后的变量可以视为局部变量存在局部变量表中。

方法内联

解决了上面两个问题之后,还要考虑一个新的问题,如果创建完一个类后,需要调用这个类的方法怎么办,在Java中,有一个叫方法区的内存(jdk1.8后叫做元数据区),里面存储了类的信息,包括类的接口信息、方法代码块、父类信息等,我们调用一个实例方法的时候都是通过栈帧上的引用拿到这个引用的类型,通过类型再拿到类的元数据信息和方法代码块,这样调用方法的时候jvm就能知道执行哪一段代码,这个可以参考JVM之Java对象模型(一)/#more)。但是对象在栈上分配之后,我们没有存储对象类型,也不能知道方法的代码块在哪,就要考虑用方法内联了。

在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。

所以简单说方法内联就是把被调用方法的代码插入调用方法的代码里面,举例:
calc方法调用add方法:

1
2
3
4
5
6
7
public void calc() {
add(1,2);
}
public int add(int a, int b) {
int c = a + b;
return c;
}

方法内联优化后:

1
2
3
public void calc() {
int c = a +b
}

把add方法的代码插入到calc方法里面之后就不需要调用add方法了,就解决了开头的问题。

实验

实验所有的参数:

1
2
3
4
5
6
7
8
-Xmx5m
-Xms5m
-XX:+PrintGC
-XX:+DoEscapeAnalysis //开启逃逸分析
-XX:+EliminateAllocations //开启标量替换
-XX:+UnlockDiagnosticVMOptions //解开下面的参数的使用
-XX:+PrintInlining //打印内联方法
-XX:FreqInlineSize=1000 //常用的方法的大小内联阀值

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @author feidao Created on 2019/1/26.
*/
public class stackallocate {
private int a;

public static void main(String[] args) throws InterruptedException {
long b = System.currentTimeMillis();
// 创建大量的对象
for (int i = 0; i < 9999; i++) {
User user = new User();
user.add(1, 2);
}
long e = System.currentTimeMillis();
// 打印执行时间
System.out.println(e - b);
}
}

// User类
class User {
public Integer id = new Integer(1);
public String name = "";

public int add(int a, int b) {
int c = a + b;
return c;
}
}

实验一(逃逸分析作用)

-XX:+DoEscapeAnalysis //开启逃逸分析
关闭逃逸分析GC输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
[GC (Allocation Failure)  1929K->905K(5632K), 0.0005832 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008417 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008527 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002722 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0009154 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0010281 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003108 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0006700 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0006196 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008359 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003075 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002615 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002509 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002575 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002295 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003781 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002508 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002496 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002262 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002826 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002551 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002428 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003438 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008910 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0004062 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002480 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002588 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002586 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002270 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002307 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007516 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0005466 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003548 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003747 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002617 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002580 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003653 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002486 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0009042 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008319 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0013945 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008274 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0006136 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0012731 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002755 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0009352 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007949 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002525 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002673 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008719 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002516 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003020 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002514 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002588 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002682 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002500 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002464 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002572 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003036 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002828 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002428 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002171 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002143 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002380 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002228 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002998 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002198 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002194 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002801 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002203 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002853 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002494 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002663 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0005944 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002508 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002517 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002488 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0011515 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008612 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008015 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007607 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0006128 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0008095 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0006772 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002673 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002494 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002508 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002367 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002280 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002418 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002322 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002152 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002217 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002262 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002236 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002354 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007698 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002564 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002500 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007438 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002701 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002604 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002624 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002529 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002561 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002904 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002595 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002460 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002259 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002421 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002289 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002287 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002255 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002287 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002214 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0002308 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007659 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0011125 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0014893 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007953 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0015654 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0013525 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0010727 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0012213 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0015489 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0011354 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0017298 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0010393 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0013813 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007818 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0007928 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0015671 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0003654 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0011234 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0015632 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0046031 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0017694 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0013312 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0010821 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0012889 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0009369 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0011935 secs]
[GC (Allocation Failure) 1929K->905K(5632K), 0.0016106 secs]
.........
405

开启逃逸分析输出结果

1
2
3
4
5
6
7
8
9
10
[GC (Allocation Failure)  1023K->520K(5632K), 0.0007850 secs]
[GC (Allocation Failure) 1540K->767K(5632K), 0.0027844 secs]
[GC (Allocation Failure) 1791K->811K(5632K), 0.0006761 secs]
[GC (Allocation Failure) 1835K->867K(5632K), 0.0006262 secs]
[GC (Allocation Failure) 1891K->906K(5632K), 0.0004547 secs]
[GC (Allocation Failure) 1930K->914K(5632K), 0.0004617 secs]
[GC (Allocation Failure) 1938K->970K(5632K), 0.0015802 secs]
[GC (Allocation Failure) 1994K->898K(5632K), 0.0003214 secs]
[GC (Allocation Failure) 1922K->898K(5632K), 0.0002552 secs]
13

说明未开启逃逸分析之前,每次User对象的分配都在堆上,导致堆内存不够用,频繁进行垃圾回收。开启之后对象在栈上分配,gc大量减少。

实验二(标量替换作用)

1
-XX:+EliminateAllocations //开启标量替换

结果和实验一一样,我就不贴输出结果了。
其实判断一个对象能不能进行标量替换是比较麻烦的,不仅要判断对象里面的实例变量能不能被替换还要判断对象方法有没有被调用,被调用的方法能不能进行方法内联等等,有兴趣的同学可以试试在User类加一个HashMap的实例变量,如下代码:

1
2
3
4
5
6
7
8
9
10
class User {
public Integer id = new Integer(1);
public String name = "";
private Map map = new HashMap<>();

public int add(int a, int b) {
int c = a + b;
return c;
}
}

或者在add方法中加入复杂代码,然后调用add方法,如下代码:

1
2
3
4
5
6
7
8
9
10
11
class User {
public Integer id = new Integer(1);
public String name = "";

public int add(int a, int b) {
String s = "hello";
s = s + "world";
int c = a + b;
return c;
}
}

实验三(方法内联作用)

1
-XX:FreqInlineSize=0 //常用的方法的大小内联阀值

这个参数的意思是方法的代码大小在不超过这个值的时候可以进行方法内联,先把这个值设置为0,输出的结果也是和之前的关闭逃逸分析和关闭标量替换的结果一样。

然后在这个值设置为1000,结果是对象也在栈上进行分配了。

1
-XX:FreqInlineSize=100 //常用的方法的大小内联阀值

<参考> https://segmentfault.com/a/1190000004606059

Feidao wechat
关注我,一起打怪升级吧!