认识JVM(2)

190 阅读6分钟

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 reference from local variabl 从本地变量中加载引用,并亚茹操作数堆栈

比如istore Store int into 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”区被填满之后,会将所有对象移动到年老代中。

young_gc

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加一岁),然后被回收了。

img