通过工具和字节码带你深入理解运行时数据区
创始人
2024-01-30 07:11:35

上篇文章介绍了JVM运行时数据区的一些信息,这篇文章将通过工具和字节码加深对常用的堆和虚拟机栈部分的理解。

虚拟机栈再理解

下面通过3个简单的例子再深入了解一下虚拟机栈区域。

1. 虚拟机栈的出入栈过程

public class JVMStack {public static void main(String[] args) {methodA();}public static void methodA(){methodB();}public static void methodB(){methodC();}public static void methodC(){System.out.println("methodC()");}
}

这段代码很简单,在main方法中调用methodA方法、methodA调用methodB、methodB调用methodC,因为每个方法在运行期间在内存中都是以栈帧的形式表示,所以启动的时候虚拟机栈入栈过程如下:
main方法是线程中运行的,运行时先把main方法栈帧压入栈底,接着再陆续把methodA方法、methodB方法、methodC方法的栈帧压入虚拟机栈。

入栈过程

因为虚拟机栈后进先出,所以出栈顺序是相反的,methodC运行完出栈,接着就是methodB、methodA,直至main方法运行结束。

出栈过程

2. 栈帧执行流程
先看一段简单的代码:

public class FrameStacks {public static void main(String[] args) {FrameStacks frameStacks = new FrameStacks();frameStacks.add();}public int add(){int a = 2;int b = 3;int c = (a + b)*4;return c;}
}

这段代码很简单,就是a和b相加的结果乘以2,然后返回。那这段代码在JVM是怎么运行的呢。先看下栈帧的结构图

栈帧内存结构

因为add是个实例方法,所以局部变量表中第0位索引的变量槽默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含的参数。也可以通过javap -v FrameStacks.class 来看下局部变量表结构,可以看到第0位索引存放的是this。

局部变量表

再通过javap -c FrameStacks.class命令看下add方法的字节码:

public int add();Code:0: iconst_21: istore_12: iconst_33: istore_24: iload_15: iload_26: iadd7: iconst_48: imul9: istore_310: iload_311: ireturn

先简单了解下这几个字节码的意思:

  1. iconst_ :将一个int类型常量加载到操作数栈,n为将要操作的数值或者常量池行号
  2. istore_ :将一个int类型数值从操作数栈存储到局部变量表,n为局部变量的位置序号
  3. iload_ :将一个局部变量加载到操作栈,n为局部变量的位置序号
  4. iadd :int类型加法指令,运算后的结果自动入操作数栈
  5. imul : int类型乘法指令,运算后的结果自动入操作数栈
  6. ireturn :返回

再解释一下这段字节码的执行步骤:

0: iconst_2 -> 将a=2加载到操作数栈
1: istore_1 -> 把a从操作数栈出栈并储存到局部变量表下标为1的位置
2: iconst_3 -> 将b=3加载到操作数栈
3: istore_2 -> 把b从操作数栈出栈并储存到局部变量表下标为2的位置 
4: iload_1  -> 把a从局部变量表加载到操作数栈
5: iload_2  -> 把b从局部变量表加载到操作数栈
6: iadd     -> 把操作数栈栈顶的两个值a和b相加,相加的动作是在执行引擎做的,加完之后自动入操作数栈
7: iconst_4 -> 把常量4加载到操作数栈栈顶
8: imul     -> 把a和b的和乘以4,同样在执行引擎计算,计算之后自动入栈顶
9: istore_3 -> 把imul得到的结果c从操作数栈出栈并存储到局部变量表下标为3的位置
10: iload_3 -> 把c从局部变量表加载到操作数栈
11: ireturn -> 把操作数栈中的结果c返回

通过这些执行步骤可以发现,变量会频繁的出入操作数栈,一些运算操作也是在执行引擎进行的,操作数栈只是暂存变量。其实操作数栈就类似于我们说的缓存,出入栈就是删除和添加缓存,操作数栈是线程级别的缓存,随着线程的结束操作数栈也就over了。

3. 栈帧优化

先介绍一个工具JHSDB,JHSDB是一款基于服务性代理(Serviceability Agent,SA)实现的进程外调试工具。服务性代理是 HotSpot虚拟机中一组用于映射Java虚拟机运行信息的、主要基于Java语言(含少量JNI代码)实现的 API集合。通过JHSDB可以更好的理解栈帧优化。

3.1 JHSDB的启动
要使用必须要把sawindbg.dll复制一份到jre的bin目录下,我的jdk安装目录如下图,你的可能不一样:

jdk文件夹

jdk1.8.0_152\jre\bin目录下找到sawindbg.dll文件,复制一份到jre\bin目录下。
进入jdk1.8.0_152\lib目录,通过命令行执行java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB ,执行完时候会出现如下窗口

HSDB

3.2 修改上面例子的代码

public class FrameStacks {public static void main(String[] args) throws InterruptedException {FrameStacks frameStacks = new FrameStacks();frameStacks.add(4);}public int add(int c) throws InterruptedException {int a = 2;int b = 3;int d = (a + b)*c;Thread.sleep(Integer.MAX_VALUE);return d;}
}

修改后启动main方法

3.3 JHSDB的使用
在cmd命令行窗口输入jps 命令,找到进程ID

jps

打开JHSDB窗口
file

HotSpot process

打开之后可以看到有好几个线程启动,我们只要选择main线程就行,然后选择左上角图片是服务器按钮查看栈内存

Stack Memory

一个栈帧的开始是从Interpreted frame部分开始的。
第一个栈帧是当前正在执行的栈帧,在这里是Thread.sleep方法的栈帧,sleep方法是native方法,因此当前是本地方法栈,也从侧面证明了Hotspot虚拟机的本地方法栈和虚拟机栈是合二为一的。
第二个是方法add方法的栈帧、第三个是main方法的栈帧,可以看到add方法栈帧的局部变量表(locals area)部分和main方法栈帧的操作数栈(expression stack)有重合,也就是蓝色方框部分,这段区域就是共享部分,也是Hotspot虚拟机对栈帧的优化。

栈帧优化

堆区再理解

下面通过JHSDB工具来再理解一下堆区的内存布局。
新建一个类HeapObject

package jvm;public class HeapObject{public static void main(String[] args) throws InterruptedException {Student studentSun = new Student();studentSun.setUsername("sun");studentSun.setAge(18);for (int i = 0; i < 15; i++) {System.gc();}Student studentArron = new Student();studentArron.setUsername("Arron");studentArron.setAge(17);Thread.sleep(Integer.MAX_VALUE);}
}class Student{private String username;private Integer age;// 省略get/set方法
}

在idea中Edit Configurations中添加虚拟机启动参数-XX:+UseConcMarkSweepGC -XX:-UseCompressedOops -Xmx10m,如图:

VM option

-XX:+UseConcMarkSweepGC的作用是使用CMS垃圾收集器。这样能更好的查看堆的分代情况,关于CMS垃圾收集器可自行了解,这里不做过多解释。
-XX:-UseCompressedOops 禁止指针压缩,JHSDB对指针压缩存在缺陷,建议关闭指针压缩
-Xmx10m是设置堆的最大内存为10M,在这里是为了JHSDB加快在内存中搜索对象的速度

然后在通过jps命令查看HeapObject进程ID

Img

进程id获取到之后通过JHSDB查看具体信息,在Tools -> Object Histogram中查看类的描述信息,通过类的全限定名搜索Student类。

Object Histogram

找到之后双击查看类的描述,这里new 了两个Student对象,会看到两个对象信息。

Object of Type

然后通过下方的Inspect 按钮分别查看两个对象地址对应的哪个对象

Inspector

从Inspector中我们可以看到
studentSun对象的内存地址是0x0000000013832558
studentArron对象的内存地址是0x0000000013400000

再在Tools -> Heap Parameters中查看堆内存分代情况

Heap Parameters

eden区的起始地址:[0x0000000013400000 ~ 0x00000000136b0000)from 起始地址:[0x00000000136b0000 ~ 0x0000000013700000)to 起始地址:   [0x0000000013700000 ~ 0x0000000013750000)Tenured 起始地址: [ 0x0000000013750000 ~ 0x0000000013e00000)

对比一下studentArron对象的内存地址是在Eden区的范围内的,所以studentArron对象在Eden区,studentSun对象内存地址在Tenured区老年代的范围内,所以studentSun在Tenured区。

为什么这两个对象不在一个区呢?
这是因为在代码中显示调用了System.gc(),studentSun对象的分代年龄变大了又因为studentSun对象的引用还在被使用,所以就把它放到了Tenured区。

从这个例子中我们可以看到Hotspot堆内存结构目前使用的是分代划分,内存空间也是连续的,并且虽然Student类对象虽然是局部变量,但是实例还是在堆区分配的。

总结

本文通过JHSDB工具和字节码层面来更深入的了解JVM运行时数据区,对于JHSDB工具和字节码也只是一个简单的使用和说明,感兴趣的可以再深入了解一下。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
最安全的匿名举报方式 匿名12... 来源:中安在线8月9日,河北邢台小伙张钊(化名)在河北政务服务网12345平台举报楼上邻居涉嫌非法开...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
中国光棍有多少人 2020年9... 我国是世界上人口最多的国家,庞大的人口基数为我国的发展带来了许多的益处。但是同时,我国也是出生人口性...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...