可能Java 数组大家都很熟悉,最近我遇到了一个关于Java 数组内存分配的问题。

“基本数据类型存储在栈内存当中,对象则保存在堆内存”这句话完全是错误的。下面是个简单的例子代码:

 

javac 最大内存_堆内存

public class Test {
    public static void main(String[] argv) {
        // 静态初始化数组
        String[] names = { "Michael", "Orson", "Andrew" };
        // 动态初始化数组
        String[] animal = new String[4];
        // 让animal 指向 namens 数组所引用的数组
        names = animal;
        
        System.out.println(names.length);
        System.out.println(animal.length);
    }
}

javac 最大内存_堆内存

 

 

 “Java 数组大小是不能改变的”这可能大家都听过,那上面这段代码就有问题了,animal [] 长度为4,而names [] 数组的长度只有3,但是经过一个赋值语句,两个数组的大小就都变为4了。这不是改变了数组的大小吗? 问题就这样挡在面前了!好吧,问问技术前辈吧,就这样对数组的存储方式有了全新的认识。下面是我的一点理解:(如果有错误的,刚好被大神你看到了,也请你能够指出来。)

          上面的的 names 和 animal 不代表这个数组对象,而仅仅是数组的变量而已,和C里面的指针是一样的,这样的变量叫做引用变量。数组对象是保存在堆内存当中,大小当然是不能改变的,但是数组变量却能够指向其他的数组对象,可以看看下面这个图:

      

javac 最大内存_数组_03

 

         蓝虚线是赋值语句 names = animal; 之前 names 和 animal 数组变量指向的堆内存当中数组对象;

         红线是是赋值语句 names = animal;之后 names 和 animal 数组变量都同时指向一个数组对象。当然这时候 Java 垃圾回收机制这时候就会发现那个没人引用的数组对象然后把它带走。

         从上面还可以看到,“Michael”,"Orson","Andrew" 这些都是基本的数据类型吧。但是他们却存储在堆内存当中。  

局部变量放在栈内存当中,(像上面的 names[],animal[] 这种引用类型的变量,还有一些基本类型的变量),但应用变量所引用的对象是保存是堆内存当中的。(包括数组还有一些我们平常写的普通的类对象)

        Java在堆内存当中的对象通常是不允许直接访问的,但你可以想到直接访问的后果。为了访问堆内存当中的对象,这时候就需要引用变量这个中介。

        什么时候Java存储在栈内存中的变量是仅仅是引用变量? 什么时候它又换了身份变为货真价实的JAVA对象纳?嗯,看看下面这个例子:

  

javac 最大内存_堆内存

public class Animal {
        private String name;
        private int age;

        Animal(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public void info() {
            System.out.println(name + " " + age);
        }
    }
public class Test {

    public static void main(String[] argv) {
        // 动态初始化数组
        Animal[] animal = new Animal[2];
        Animal cat = new Animal("cat", 1);
        Animal dog = new Animal("dog", 2);
        animal[0] = dog;
        animal[1] = cat;

        // 当数组变量引用对象的方法(或者属性)的时候,它就变为实际的Java 对象
        System.out.println(animal.length);
        //dog 这个原本存储在栈内存当中的对象引用通过调用对象的方法变为实际的对象
        dog.info();
        animal[0].info();
    } 
}

javac 最大内存_堆内存

       只有当栈内存中的引用变量调用了对象的方法或者是指向了对象的属性的时候,它就从变量真正成了对象了。(比如上面例子中的 cat,dog 对象引用变量,animal[]数组变量)。

       通过

animal[0] = dog;
        animal[1] = cat;

       使得两个变量都指向了存储在堆内存当中的对象,所以他们俩个打印出来的信息是一模一样的。


    上图中蓝线是赋值语句:
animal[0] = dog;
        animal[1] = cat;
          之前的变量指向的状态,红虚线是赋值语句之后的状态,animal[0]和dog ,animal[1] 和cat 所指向的都是相同的堆内存空间。

   (PS:我还是要感谢这几个月来那几个面试官对我从头到尾的虐,虽然现在实习的事情还没有个准信,当我发现我要走的路还很长很长,上面这个问题的起因也是一个面试官的提问,“你知道Java 当中数组是怎样存储的吗?”)