局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并无明确指明一个Slot暂用的内存空间大小,只是颇有“导向性”地说明每一个Slot都应该能存放一个boolean,byte,char,short,int,float,refrence,returnAddress类型的数据,这种描述明确指出 “每一个Slot占用32位长度的内存空间” 有一些差异,它容许Slot的长度随着处理器,操做系统或虚拟机的不一样而发生变化。不过不管如何,即便在64位虚拟机中使用64位长度的内存空间来实现Slot,虚拟机仍要使用对齐和补白的手段让Slot在外观上看起来和32位虚拟机中得一致。java
既然前面提到了数据类型,在此顺便说一下,一个Slot能够存放一个32位之内的数据类型,Java中用32位之内的数据类型有:boolean,byte,char,short,int,float,reference,returnAddress八种类型。reference是对象的引用。虚拟机规范即没有说明它的长度,也没有明确指出这个引用应由怎样的结构,通常来讲,虚拟机实现至少都应当能今后引用中直接或间接的查找到对象在Java堆中得起始地址索引和方法区中得对象类型数据。而returnAddress是为字节码指令jsr,jsr_w 和 ret服务的。它指向了一条字节码指令的地址。数组
对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的Slot空间。Java语言中明确规定的64位的数据类型只有long和double数据类型分割存储的作法与"long和double的非原子性协定" 中把一次long 和double 数据类型读写分割为两次32位读写的作法相似,在阅读JAVA内存模型时对比下。不过,因为局部变量表建在线程的堆栈上,是线程私有的数据,不管读写两个连续的Slot是不是原子操做,都不会引发数据安全问题。安全
虚拟机经过索引定位的方式使用局部变量表,索引值的范围是从0开始到局部变量表最大的Slot数量。若是32位数据类型的变量,索引N就表明了使用第N个Slot,若是是64位数据类型的变量,则说明要使用第N个和N+1两个Slot。ide
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程。若是是实例方法(非static的方法),那么局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中能够经过关键字this来访问这个隐含的参数,其他参数则按照参数表的顺序来排列,暂用从1开始的局部变量Slot,参数表分配完毕后,在根据方法体内部定义的变量顺序和做用域分配其他的Slot。优化
局部变量表中得slot是可重用的,方法体定义的变量,其做用域并不必定会覆盖整个方法体,若是当前字节码PC计数器的值已经超过了某个变量的做用域,那么这个变量对应的Slot就能够交给其余变量使用。这样的设计不只仅是为了节省栈空间,在某些状况下Slot的复用会直接影响到系统的垃圾收集行为。例如以下代码:
this
[GC 66558K->65952K(129024K), 0.0015650 secs]编码
[Full GC 65952K->65853K(129024K), 0.0122710 secs]lua
从运行结果分析,发现System.gc()运行后并无回收掉这64M的内存。url
没有回收掉"_64M"的内存能说的过去,由于在执行System.gc()时,变量_64M还处于做用域以内,虚拟机天然不敢回收掉该内存。咱们把代码位以下:[GC 66558K->65968K(129024K), 0.0014760 secs]spa
[Full GC 65968K->65853K(129024K), 0.0127180 secs]
这是为何呢?
在解释为何以前,咱们先对代码进行第二次修改。在调用 System.gc()以前加入代码int x=0, 这个修改看起来莫名其妙,但运行如下程序,却方法此次内存针对被正确回收了。
[GC 66558K->65936K(129024K), 0.0027120 secs]
[Full GC 65936K->317K(129024K), 0.0129600 secs]
局部变量"_64M"可否被回收的根本缘由就是:局部变量表中得Slot是否还存有关于_64M数组对象的引用。第一次修改,代码虽然离开了_64的做用域,但在此以后,没有任何对局部变量表的读写操做,_64M 本来所占用的Slot尚未被其余变量所复用,因此做为GC Roots 一部分的局部变量表让然保持对它的关联。这种关联没有被及时打断,在绝大部分状况下都很轻微。但若是遇到一个方法,其后面的代码有一些耗时很长的操做,而前面又占用了大量的内存,实际上已经不会在被使用的变量,手工将其设置为NULL值(用来代替int x=0)把变量对应的局部变量表Slot状况,就不是一个毫无心义的操做,这种操做能够做为 一种在及特殊情形(对象暂用内存大,此方法的栈帧长时间不能被回收,方法调用次数达不到JIT编译条件)下得“奇技” 来使用。但不该当对赋null值操做有过多的依赖,也没有必要把它当作一个广泛的编码方法来推广,以恰当的变量做用域来控制变量回收时间才是最优雅的解决方法。另外,赋null值的操做在通过虚拟机JIT编译器优化以后会被消除掉,这时候将变量设置为null其实是没有意义的。字节码被编译为bending代码后,对GC Roots的枚举也与解释执行时期有所差异,在通过JIT编译后,System.gc()执行时就能够正确的回收掉内存。
打印GC详细日志还能够加上参数:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
[GC 66558K->65952K(129024K), 0.0015650 secs]
[Full GC 65952K->65853K(129024K), 0.0122710 secs]
从运行结果分析,发现System.gc()运行后并无回收掉这64M的内存。
没有回收掉"_64M"的内存能说的过去,由于在执行System.gc()时,变量_64M还处于做用域以内,虚拟机天然不敢回收掉该内存。咱们把代码位以下:[GC 66558K->65968K(129024K), 0.0014760 secs]
[Full GC 65968K->65853K(129024K), 0.0127180 secs]
这是为何呢?
在解释为何以前,咱们先对代码进行第二次修改。在调用 System.gc()以前加入代码int x=0, 这个修改看起来莫名其妙,但运行如下程序,却方法此次内存针对被正确回收了。
[GC 66558K->65936K(129024K), 0.0027120 secs]
[Full GC 65936K->317K(129024K), 0.0129600 secs]
局部变量"_64M"可否被回收的根本缘由就是:局部变量表中得Slot是否还存有关于_64M数组对象的引用。第一次修改,代码虽然离开了_64的做用域,但在此以后,没有任何对局部变量表的读写操做,_64M 本来所占用的Slot尚未被其余变量所复用,因此做为GC Roots 一部分的局部变量表让然保持对它的关联。这种关联没有被及时打断,在绝大部分状况下都很轻微。但若是遇到一个方法,其后面的代码有一些耗时很长的操做,而前面又占用了大量的内存,实际上已经不会在被使用的变量,手工将其设置为NULL值(用来代替int x=0)把变量对应的局部变量表Slot状况,就不是一个毫无心义的操做,这种操做能够做为 一种在及特殊情形(对象暂用内存大,此方法的栈帧长时间不能被回收,方法调用次数达不到JIT编译条件)下得“奇技” 来使用。但不该当对赋null值操做有过多的依赖,也没有必要把它当作一个广泛的编码方法来推广,以恰当的变量做用域来控制变量回收时间才是最优雅的解决方法。另外,赋null值的操做在通过虚拟机JIT编译器优化以后会被消除掉,这时候将变量设置为null其实是没有意义的。字节码被编译为bending代码后,对GC Roots的枚举也与解释执行时期有所差异,在通过JIT编译后,System.gc()执行时就能够正确的回收掉内存。
打印GC详细日志还能够加上参数:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps