官网 : https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
每个栈帧中包括:
局部变量表,操作数栈,动态链接,方法返回地址
局部变量表 : 方法中定义的局部变量以及方法的参数存放在这张表中局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。 (局部变量以0,1,2这样的顺序存在)
操作数栈 : 以压栈和出栈的方式存储操作数的
动态链接 : 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接 (Dynamic Linking) 。
方法返回地址 : 当一个方法开始执行后 , 只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。
class Person {private String name = "Jack" ;private int age ;private final double salary = 100 ;private static String address ;private final static String hobby = "Programming" ;public void say (){System . out . println ( "person say..." );}public static int calc ( int op1 , int op2 ){op1 = 3 ;int result = op1 + op2 ;return result ;}public static void order (){}public static void main ( String [] args ){calc ( 1 , 2 );order ();}}
javap 反编译获得指令
此时你需要一个能够看懂反编译指令的宝典比如官网的: https://docs.oracle.com/javase/specs/jvms/se8/html/index.html当然网上也有很多,大家可以自己查找
class
Person
{
...
public static
int
calc
(
int
,
int
);
Code
:
0
:
iconst_3
//
将
int
类型常量
3
压入
[
操作数栈
]
1
:
istore_0
//
将
int
类型值存入
[
局部变量
0]
2
:
iload_0
//
从
[
局部变量
0]
中装载
int
类型值入栈
3
:
iload_1
//
从
[
局部变量
1]
中装载
int
类型值入栈
4
:
iadd
//
将栈顶元素弹出栈,执行
int
类型的加法,结果入栈
【
For example
,
the iadd instruction
(
§iadd
)
adds two
int
values together
.
It
requires that the
int
values to be added be the top two values of the operand stack
,
pushed
there by previous instructions
.
Both of the
int
values are popped from the operand stack
.
They are added
,
and their sum is pushed back onto the operand stack
.
Subcomputations may be
nested on the operand stack
,
resulting in values that can be used by the encompassing
computation
.
】
5
:
istore_2
//
将栈顶
int
类型值保存到
[
局部变量
2]
中
6
:
iload_2
//
从
[
局部变量
2]
中装载
int
类型值入栈
7
:
ireturn
//
从方法中返回
int
类型的数据
...
}
![](https://i-blog.csdnimg.cn/blog_migrate/1d642b6c25c63ebfa6830904649c9f04.png)
思考一下
1.栈可以指向堆 ,like Object a=new Object();在方法中new
2.方法区指向堆, 静态变量static Object aa=new Object(();
3.堆指向方法区,Class对象的类信息就是存储在方法区中
4 Java对象内存布局
一个Java对象在内存中包括3个部分:对象头、实例数据和对齐填充
对齐填充->如何扩容
内存模型
一块是非堆区,一块是堆区。
堆区分为两大块,一个是
Old(对象会存在比较长时间,标记清除,标记-整理算法)
区,一个是
Young区(大多朝生夕死,只有少数会存活,复制算法)。
Young
区分为两大块,一个是
Survivor
区(
S0+S1
),一块是
Eden区。 Eden:S0:S1=8:1:1
S0
和
S1
一样大,也可以叫
From
和
To
。
![](https://i-blog.csdnimg.cn/blog_migrate/95399eb5bae5bcb1042a1e11040d2d32.png)
对象创建所在区域
一般情况下,新创建的对象都会被分配到
Eden
区,一些特殊的大的对象会直接分配到
Old
区。
比如有对象 A , B , C 等创建在 Eden 区,但是 Eden 区的内存空间肯定有限,比如有 100M ,假如已经使用了100M 或者达到一个设定的临界值,这时候就需要对 Eden 内存空间进行清理,即垃圾收集 (Garbage Collect) ,这样的 GC 我们称之为 Minor GC , Minor GC 指得是 Young 区的 GC 。经过 GC 之后,有些对象就会被清理掉,有些对象可能还存活着,对于存活着的对象需要将其复制到 Survivor区,然后再清空 Eden 区中的这些对象。
Survivor
区详解
由图解可以看出,
Survivor
区分为两块
S0
和
S1
,也可以叫做
From
和
To
。
在同一个时间点上,
S0
和
S1
只能有一个区有数据,另外一个是空的。(复制算法,解决空间碎片问题,但浪费了空间)
接着上面的 GC 来说,比如一开始只有 Eden 区和 From 中有对象, To 中是空的。此时进行一次 GC 操作, From 区中对象的年龄就会 +1 ,我们知道 Eden 区中所有存活的对象会被复制到 To 区,From 区中还能存活的对象会有两个去处。若对象年龄达到之前设置好的年龄阈值,此时对象会被移动到 Old 区, 如果Eden区和From区 没有达到阈值的对象会被复制到 To 区。 此时 Eden 区和 From 区已经被清空 ( 被 GC 的对象肯定没了,没有被 GC 的对象都有了各自的去处 ) 。这时候 From 和 To 交换角色,之前的 From 变成了 To ,之前的 To 变成了 From 。也就是说无论如何都要保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,知道 To 区被填满,然后会将所有对象复制到老年代中。
Old
区详解
从上面的分析可以看出,一般 Old 区都是年龄比较大的对象,或者相对超过了某个阈值的对象。在 Old 区也会有 GC 的操作, Old 区的 GC 我们称作为 Major GC 。
对象的一辈子理解
一个普通的
Java
对象
,
出生在
Eden
区
,
在
Eden
区我还看到和我长的很像的小兄弟
,
我们在
Eden
区中玩了挺长时间。有
一天
Eden
区中的人实在是太多了
,
我就被迫去了
Survivor
区的
“From”
区
,
自从去了
Survivor
区
,
我就开始漂了
,
有时候在
Survivor
的
“From”
区
,
有时候在
Survivor
的
“To”
区
,
居无定所。直到我
18
岁的时候
,
爸爸说我成人了
,
该去社会上闯闯
了。
于是我就去了年老代那边
,
年老代里
,
人很多
,
并且年龄都挺大的
,
我在这里也认识了很多人。在年老代里
,
我生活了
20
年
(
每次
GC
加一岁
),
然后被回收。
![](https://i-blog.csdnimg.cn/blog_migrate/23d69019831ee32b796337fe91ff7145.png)
常见问题
Minor GC: 新生代Major GC: 老年代Full GC: 新生代 + 老年代
为什么需要
Survivor
区
?
只有
Eden
不行吗?
如果没有 Survivor,Eden 区每进行一次 Minor GC , 并且没有年龄限制的话 , 存活的对象就会被送到老年代。这样一来,老年代很快被填满 , 触发 Major GC( 因为 Major GC 一般伴随着 Minor GC, 也可以看做触发了 Full GC) 。老年代的内存空间远大于新生代 , 进行一次 Full GC 消耗的时间比 Minor GC 长得多。执行时间长有什么坏处 ? 频发的 Full GC 消耗的时间很长 , 会影响大型程序的执行和响应速度。可能你会说,那就对老年代的空间进行增加或者较少咯。假如增加老年代空间,更多存活对象才能填满老年代。虽然降低 Full GC 频率,但是随着老年代空间加大 , 一旦发生 FullGC, 执行所需要的时间更长。假如减少老年代空间,虽然 Full GC 所需时间减少,但是老年代很快被存活对象填满 ,Full GC 频率增加。所以 Survivor 的存在意义 , 就是减少被送到老年代的对象 , 进而减少 Full GC 的发生 ,Survivor 的预筛选保证 , 只有经历 16次 Minor GC 还能在新生代中存活的对象 , 才会被送到老年代。
为什么需要两个
Survivor
区?(s0,s1)
最大的好处就是解决了碎片化。也就是说为什么一个 Survivor 区不行 ? 第一部分中 , 我们知道了必须设置 Survivor 区。假设现在只有一个 Survivor 区 , 我们来模拟一下流程 :刚刚新建的对象在 Eden 中 , 一旦 Eden 满了 , 触发一次 Minor GC,Eden 中的存活对象就会被移动到 Survivor 区。这样继续循环下去 , 下一次 Eden 满了的时候 , 问题来了 , 此时进行 Minor GC,Eden 和 Survivor 各有一些存活对象 , 如果此时把 Eden 区的存活对象硬放到 Survivor 区 , 很明显这两部分对象所占有的内存是不连续的 , 也就导致了内存碎片化。永远有一个 Survivor space 是空的 , 另一个非空的 Survivor space 无碎片。
新生代中
Eden:S1:S2
为什么是
8:1:1
?
新生代中的可用内存:复制算法用来担保的内存为 9 : 1可用内存中 Eden : S1 区为 8 : 1即新生代中 Eden:S1:S2 = 8 : 1 : 1
体验与验证
使用
jvisualvm
查看
visualgc
插件下载链接
:
https://visualvm.github.io/pluginscenters.html
--->
选择对应版本链接
--->
Tools
--->Visual GC
![](https://i-blog.csdnimg.cn/blog_migrate/a46e132a158b81bc2a295e135f720d83.png)