一个栈帧需要分配多少内存并不会受到程序运行时变量的影响,只取决于源码和虚拟机实现方式
记录问题:书中说局部变量表的复用可能会导致垃圾回收不及时,做实验看一下
static void test1() {
{
byte[] placeholder = new byte[1024*1024*64];
}
System.gc();
}
这一段代码用 -verbose:gc -XX:+UseSerialGC -Xlog:gc* 查看垃圾回收情况会发现应该被回收的placeholder确实没被回收,而且由于是一个大对象直接进入了老年代,如下图所示
然后我们如书中修改代码,加上一个没头没脑呢变量i
static void test1() {
{
byte[] placeholder = new byte[1024*1024*64];
}
int i = 0;
System.gc();
}
发现正如书中所说变量i 复用了placeholder的变量槽,所以导致placeholder 变为GC Roots不可达;而之前的代码中由于gc()之前变量槽没有发生改变,所以placeholder依然可达,也就暂时不会被收集
书中到这里就结束了,但是还有一点点要说的,我自己改代码的时候本来是改成下面这样的
static void test1() {
{
byte[] placeholder = new byte[1024*1024*64];
}
int i;
System.gc();
}
但是发现没效果,一样是不回收placeholder(如下图)
考虑到局部变量如果不赋值就完全不能使用,那是不是也不会在局部变量表中分配位置呢?查看一下编译的字节码发现不但没有相关的代码,常量池和局部变量表中也没有变量i 的信息,到这就可以判断经过编译器的优化后这个未声明的变量i 直接不存在了
方法的调用:invokestatic 静态方法; invokespecial 实例构造器,私有方法,父类中的方法; invokevirtual 虚方法; invokeinterface 接口方法; invokedynamic 动态解析调用点限定符所引用的方法
分派
public class DispatchTest {
static abstract class Human {}
static class Man extends Human {}
static class Woman extends Human {}
void say(Human h) {System.out.println("Human");}
void say(Man h) {System.out.println("Man");}
void say(Woman h) {System.out.println("Woman");}
void test() {
Human h = new Man(), h2 = new Woman();
say(h);
say(h2);
}
void example() {Human h = new Random().nextBoolean() ? new Man() : new Woman(); }
public static void main(String[] args) {
new DispatchTest().test();
}
}
这里是静态分派,从example() 可以看到编译器其实不知道h的实际类型,只能知道静态类型为Human,所以会执行say(Human h) 方法
这里原来挺疑惑的,为什么重载不能像重写一样使用动态分配,后来感觉可能因为在编译时重载至少知道最初父类的方法中有没有这个方法,有这个信息就可以顺着继承链在运行时找到相应的方法;而重写没办法判断具体是哪一个,在上面例子中只知道是say方法,如果在这个时候不决定到了运行时就可能出现传入了一个错误的类型导致错误,而这种错误在Java中是要在编译时报出的,所以如果想在编译时绑定重写方法,只有绑定到静态类型上。这段都是我的猜测,欢迎指正
这里还有一个和C++不一样的,Java的字段没有多态性,不能被继承。
Java是静态多分派,动态单分派的语言:在编译器中静态分派时要更具静态类型和方法参数,运行中动态分配时由于方法参数已经确定了所以只需要考虑方法接收者的实际类型