1.结合javap命令理解栈帧
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。下面是其用法说明:
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
编写一个简单的java代码
public class MathTest(){
private static Integer MAX=666;
public Integer compute (){
int a=1;
int b=2;
int c=a+b;
return c;
}
public static void main(String[] args) {
MathTest test=new MatchTest();
test.compute();
}
}
解析MathTest.class文件
javap -c MathTest.class->MatchTest.txt
得到以下汇编指令
Compiled from "MathTest.java"
public class MathTest {
public MathTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.Integer compute();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: iload_3
9: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class MathTest
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #5 // Method compute:()Ljava/lang/Integer;
12: pop
13: return
static {};
Code:
0: sipush 666
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: putstatic #6 // Field MAX:Ljava/lang/Integer;
9: return
}
虚拟机指令集在官网上有详细解释 :docs.oracle.com/javase/spec…
比如iconst 将
int常数< i >(-1、0、1、2、3、4或5)推入操作数堆栈。比如aload Load
referencefrom local variabl 从本地变量中加载引用,并亚茹操作数堆栈比如istore Store
intinto local variable 把int类型的值存入本地变量
根据 0: iconst_1指令, 将int类型3推送至栈顶可以得到下图
根据istore_1指令,将上面int类型1的数值出栈,并存入第二个本地变量,如下图
指令2: iconst_2,3: istore_2和上面指令类型,指令执行完之后如下图所示:
指令 4: iload_1,5: iload_2 将从局部变量加载,并压入堆栈
指令6: iadd,7: istore_3,8: iload_3 上面压入操作数栈后,执行add操作,并且把相加的值放入第4个变量中,再入栈
指令9: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: areturn 执行Integer的valueOf方法,因为返回值是Integer需要包装,返回引用c
2.JVM内存模型
jvm内存模型中,一块是非堆区,一块是堆区
堆区分为两大块,一个是old区,一个是yong区
Yong区分为两大块,一个是survivor区(s1+s0),一块是eden区,Eden:s0:s1=8:1:1
s0和s1一样大,也可以叫from和to
下面是示意图
3.1对象创建所在的区域
一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到old区
比如有对象A,B,C创建在Eden区,但是Eden区的内存空间肯定有限,比如有100M,假如已经使用了100M或者达到一个设定的临界值,这时候就需要对Eden区内存空间进行清理,即垃圾收集(Garbage Collect),这样的GC我们称之为Minor Gc,MinorGc指的是Young区的gc
经过GC之后,有些对象就会被清理掉,有些对象可能还存活着,对于存活的对象需要将其复制到survivor区,然后再请客Eden区中的这些对象
3.2survivor区详解
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
3.3Old区详解
从上面的分析可以看出,一般的old区都是年龄比较大的对象,或者相对超过了某个阈值的对象,
在old区也会有GC的操作,Old区的GC我们成为Major Gc
3.4对象的一辈子理解
我是一个普通的 Java 对象,我出生在 Eden 区,在 Eden 区我还看到和我长的很像的小兄弟,我们在 Eden 区中玩了挺长时间。
有一天,Eden 区中的人实在是太多了,我就被迫去了 Survivor 区的 “From” 区。
自从去了 Survivor 区,我就开始飘了,有时候在 Survivor 的 “From ” 区,有时候在Survivor的 “To” 区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。
于是我就去了老年代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次
GC加一岁),然后被回收了。