前段时间学习二叉树在处理删除操作的时候遇到一个头疼的问题:删除节点的时候明明已经置null了可树上该节点依旧存在,还必须执行node.father.left = null;才可以删除node节点,寻找了一下原因发现还是因为对java内存管理理解不够深入。
代码如下:
@Test
public void testNode() {
Node node1 = new Node("node1");
Node node2 = new Node("node2");
node2.father = node1;
node1.next = node2;
changeNode(node1.next);
System.out.println(node1.next.name);
}
private void changeNode(Node node3) {
node3 = null;
}
运行代码之后发现本来已经在changeNode()中已经设置node3 = null;可依旧能输出 node1.next.name。
要知为何?先理解几个概念:
1、栈内存存储基本类型的变量和对象的引用变量。
2、堆内存用于存放由new创建的对象和数组。每new一个对象就在堆内存中开辟一个新的存储空间存储此实例对象。
3、Person p = new Person();
执行new命令时程序执行两步:a:在堆内存中开辟一段空间,存储new出来的对象;b:在栈内存中添加一个变量p,p中存放的是该对象在堆内存中开始存放处的物理地址。
4、p = null;
执行此步骤的时候程序只是更改栈内存中的P变量所保存的地址,把地址指向null,而并没有操作堆内存(把p所指向的对象实例清空回收)。
5、无论是形参或者实参,执行 XXX = null;操作时都是把XXX变量栈中存储的地址改为指向null的地址。不操作堆中的数据。
下面就分析一下每步代码在堆内存和栈内存中的变化:
1、当new一个对象的时候,程序首先在堆内存中开辟一段空间实例化对象,同时在在栈内存中加入一个node1的变量,此变量中保存的是堆内存中物理地址的首地址。
2、当调用方法传入参数的时候,在栈内存中添加一个局部变量node3,存储node2的物理首地址。也即是把node2的值0X00011拷贝进node3的栈内存中。
有网友说这是引用传递,其实传递的还是值,只不过node2的值本来就是物理地址,然后把这个物理地址值传给node3.
3、当执行node3 = null; 的时候,程序会把node3中的0X00011变为一个指向null的地址 0Xaaaaa.但是程序不对堆内存中数据进行管理(堆内存中没有引用指向的数据会在一定的时间通过GC进行处理)。
可以看到执行到此步只是把变量中的物理地址置空,但并没有删除node2在堆内存中的数据,这正是为什么删除之后节点之后原数据依旧存在的原因。可以看到node1中的next依旧指向0X00011。
即使执行node2 = null,程序依旧不改变堆内存。
原数据依旧存在!
JVM内存处理实际情况要远远比这复杂得多,如果想深入了解可以找找 jvm内存处理机制相关资料,网上一搜一大堆,不再赘述。其实我也不知道。