Java虚拟机——运行时数据区域
目录
总览
- 线程共享,那么其数据也可以共享,但是有可能会引发线程安全问题
- 线程不共享,一般不会引发线程安全问题,但是数据却是不共享的

程序计数器
从上面的图中可以看到,程序计数器是线程不共享的
概念
程序计数器保存了接下来要执行的字节码指令的地址,当该字节码指令执行完成之后,程序计数器中便会存放下一个要执行的字节码指令的地址

作用
- 作用一:就是同概念中所讲一样,程序计数器会存放下一个要执行的字节码指令的地址
- 作用二:针对在高并发的情况,由于cpu执行时并不是串行执行,所以需要程序计数器记录每个线程接下来将要执行的字节码指令的地址,这样才能使cpu正确执行对应的字节码指令
问题
程序计数器会出现内存溢出吗?
- 首先要知道什么是内存溢出。程序在使用某一块内存区域时,存放数据的内存超过了虚拟机的最大内存上限。显而易见,程序计数器时不会出现内存溢出的,因为它始终存放的都是接下来要执行的字节码的指令的地址,所以不会发生内存溢出
栈
Java虚拟机栈
概念

例子:
- 首先程序执行main方法,main方法的栈帧进栈
- 接着执行study方法,study方法的栈帧进栈
- 接着执行eat方法,eat方法的栈帧进栈,打印出“吃饭”,然后eat方法执行完毕,eat方法的栈帧出栈
- 接着执行sleep方法,sleep方法的栈帧进栈,打印“睡觉”,然后sleep方法执行完毕,sleep方法的栈帧出栈
- study方法执行完毕,study方法的栈帧出栈
- main方法执行完毕,main方法的栈帧出栈,程序结束

销毁

栈帧的组成

局部变量表
- 作用

- 栈帧中的局部变量表
这里注意:long和double类型占用两个槽。字节码文件中的局部变量表中的序号对应栈帧中的局部变量表中变量的起始位置。
如果又声明了一个变量 int a=0; ,那么该变量对应字节码文件中的局部变量表中的序号应该为3(因为j占用两个槽)

- 实例方法


- 思考题
以下代码的局部变量表中会占用几个槽

一般思路:首先 this 占一个,k占一个,m占一个,a占一个,b占一个,c占一个,i占一个,j占两个,一共9个槽,这个其实是不正确的
实际上:总共占用6个槽,因为在存放局部变量时会有一个优化。为了节省空间,局部变量表中的槽是可以进行复用的,一旦某个局部变量不再生效,当前槽就可以再次被使用。
分析:首先,this 占一个,k占一个,m占一个,这个不用多说;然后a,b将存入3和4的位置;接着存c,因为a,b都没有被使用到,所以c将被存到3的位置;接着存i,由于c没有被使用到,那么i将存放到3的位置,j存放到4和5的位置。总计占用6个槽

操作数栈
了解一下即可

- 案例:计算操作数栈的最大深度

分析一下字节码执行的过程,操作数栈的最大深度就可以轻松得出
首先,iconst_0执行,将0压入操作数栈,然后执行istore_1,将0存入局部变量表的1号位置,然后执行iload_1,将局部变量表1号位置的数据加载到操作数栈中,接着执行iconst_1,将1压入操作数栈中,执行iadd,将栈顶的两个元素相加,然后再放回操作数栈中,此时操作数栈中只有一个变量1,然后执行istore_2,将栈顶元素1存入局部变量表的2号位置,return结束执行。
分析可知,在执行时,操作数栈最多容纳的元素为2,那么操作数栈的最大深度为2
帧数据
- 动态链接
字节码指令12行表示调用System.out的静态变量,进行打印操作

- 方法出口
看着解释或许会有点拗口,其实意思就是(以下面这个为例),当sleep方法执行结束后,sleep方法的栈帧就会弹出虚拟机栈,接着就会执行study方法,但是程序计数器并不知道到底该执行study方法中的第几行指令,所以需要sleep方法的栈帧中存储执行下一条指令的地址,在sleep方法的栈帧将要被弹出虚拟机栈时,将该地址存入程序计数器,以便后续程序的执行。

- 异常表

例子:
起始PC与结束PC,表示从哪一行字节码指令开始到哪一行字节码指令结束捕获异常,跳转PC表示,程序出现异常,将要跳转到第几行字节码指令。第七行中astore_1,表示将刚刚捕获的异常对象的引用放在局部变量表的一号位置。

栈内存溢出
概览
由于Java虚拟机栈内存是有限的,如果虚拟机栈中存放的栈帧过于多,就会导致栈内存溢出,线程也会直接结束

虚拟机栈内存默认大小

设置大小

注意
- 有都虚拟机对栈的内存大小做了限制,如果手动设置的值不满足该范围,那么手动设置将会失效

- 局部变量的影响

- 建议

本地方法栈

堆
概览

堆内存的溢出

内存结构
这里有可能不是太理解。used与max无需解释,还是很好理解的。total表示java虚拟机已经分配的可用堆内存,例如,一个java虚拟机max为7G,那么total可以暂时分配1G,如果不够用,可以继续分配,但是不能超过max

注意:并不是used=total=max时,才会抛出堆内存溢出异常,实际情况往往达到该条件更早,这与虚拟机垃圾回收有关系,具体什么原因,之后可以解释一下
设置


- 建议
建议将 -Xmx 与 -Xms 设置为相同的值,这样可以减少程序所使用的内存太小或者太大,java虚拟机给total缩小或者扩张的时间
方法区
概览

元信息

运行时常量池

不同版本方法区的设计

设置大小

- 设置建议:如果一台服务器上部署多个Java应用,由于元空间是位于操作系统维护的直接内存中,可能会导致不同应用之间因为元空间问题而导致性能有所影响,所以建议最好设置一下元空间最大大小
字符串常量池
概览
new出来的“abc”,将会被放到堆中,s1保存其地址,s2则保存字符串常量池中的地址,显然二者不相等

intern方法
String.intern()方法,可以手动的将字符串放入字符串常量池中
讨论一个问题:
该程序应该打印出什么样的结果?

注意:字符串“java”在类加载时会直接加载到字符串常量池中
在JDK6中,

所以看如下图解

显然,应该打印出来两个false
那么在JDK7及之后的版本,

所以看如下图解

显然,应该打印出来一个true,一个false
直接内存
概念

创建使用直接内存

设置直接内存的大小

Java虚拟机运行时数据区域详解
1989

被折叠的 条评论
为什么被折叠?



