局部变量表

局部变量表我在之前提过,实际上就是方法运行时的变量表,已经在.class文件中存在了,但是我们还需要扣一下细节,Java虚拟机并没有规定变量的大小

只是说一个slot(数据的最小单位),必须可以存放一个boolean,byte,char,short,int,float,reference(引用类型)

实际上就是指一个slot至少可以存放一个32位的数据,即,4字节,对于引用类型来说可能不一定小于等于32位

因为虚拟机实现不同,如果引用类型所包含的信息足够大,是有可能超过32位的,如,锁的信息,对象的年龄代信息,对象所指向的内存信息等等

但是基本上目前的虚拟机经过一系列的优化,基本都可以控制在4字节之内完成全部信息的存储,所以这篇文章默认引用类型的长度小于等于4字节

至少可以,说明是大于等于,如果有哪个jvm设置slot为64位,也是可行的(实际上没人这样干,毫无意义的浪费内存)

而除去上面的这几种类型,还有两种没有提到,即,long和double,Java虚拟机将他们规定为64位的数据,

但是这里有一个问题,long是64位的很好理解,但是为什么double也是64位呢?

从范围来看,明显double已经超过64位可以容纳的最大长度相当多,我翻了很多网上的博客和论坛

网上的博客和论坛大部分是说是因为double采用了特殊的数据存储,但是大多讲的不好,我在这里用自己的话说一下吧

查看Java Double(即,double包装类)源码,可知0x1.fffffffffffffP+1023是double的最大长度,那么为什么是这个值呢?

将他转换为二进制

可以把1.fffffffffffff看做二进制的1(请忽略那个点)—1111(即二进制对于16进制的f)—1111*12(f太多了,省略)

也就是说前面的1.fffffffffffff实际上在二进制中占4*13+1位,共53位,而1023转换为二进制数,即为11–1111—1111

即,10位数,加上前面的53位,共63位,补上一位符号位,即为64位,这就是为什么double是64位的原因,在Java中double实际上是将两个数据存放在double中了

这是double的数据模型

位置 意义
1 符号位
2-54 精确数值
55-64 科学计数位

也就是说在doble中可靠数据最大只有17976931348623157即,2的53次方-1,同为浮点类型的float同理

而在jvm中通常把上文这两种64位的数据存放在两个slot中

也就是说,如果我们需要修改double中的数据,实际上需要操作两个slot,Java虚拟机明确规定了,如果字节码只操作其中一个slot,抛出异常

为了节省内存,slot是可以复用的,对于局部变量来说,如果一个变量离开了他的作用域范围,其他变量就可以复用这个slot

示例代码

public class Test {
    public static void main(String[] args) {
        if(true){
            byte[] bs=new byte[1024*1024*1024];
        }
        System.gc();
    }
}

使用-verbose:gc开启gc打印,结果
局部变量表_第1张图片
可以看到,jvm虚拟机并没有回收内存,让我们回忆一下,jvm的回收算法是可达算法,可达算法最重要是事情就是确定一个变量是否可达,打开Javap反编译查看

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // int 1073741824
         2: newarray       byte
         4: astore_1
         5: invokestatic  #3                  // Method java/lang/System.gc:()V
         8: return
      LineNumberTable:
        line 4: 0
        line 6: 5
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;

很明显,对于.class文件来说,byte数组的作用域似乎是不存在的,对象始终被指向bs变量(因为bs变量属于局部变量,而且没有使用,所以经过代码优化后,局部变量表中并没有他,但是不影响我们理解)

这涉及到编译器优化,将if(true)这种语句优化了,但还涉及上面说的slot复用,增加一行代码

public class Test {
    public static void main(String[] args) {
        if(true){
            byte[] bs=new byte[1024*1024*1024];
        }
        int i=0;
        System.gc();
    }
}

局部变量表_第2张图片
回收成功,查看反编译.class

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // int 1073741824
         2: newarray       byte
         4: astore_1
         5: iconst_0
         6: istore_1//复用slot
         7: invokestatic  #3                  // Method java/lang/System.gc:()V
        10: return
      LineNumberTable:
        line 4: 0
        line 6: 5
        line 7: 7
        line 8: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            7       4     1     i   I

可以看出来,bs对象所占用的slot被i复用了,因此,bs所对应的数组对象失去了引用被jvm顺利回收,这就是slot的复用,到此,局部变量表讲解完成

你可能感兴趣的:(局部变量表)