局部变量表我在之前提过,实际上就是方法运行时的变量表,已经在.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打印,结果
可以看到,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();
}
}
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的复用,到此,局部变量表讲解完成