面试题:Java对象不再使用时,为什么要赋值为null?

来历:Java面试那些事儿

链接:https://dwz.cn/XbkyUm0L

# 前语

许多Java开发者都曾听说过“不运用的目标应手动赋值为null“这句话,并且很多开发者一向信仰着这句话;问其原因,大都是答复“有利于GC更早收回内存,削减标签17内存占用”,但再往深化问就答复不出来了。

鉴于网上有面试题:Java目标不再运用时,为什么要赋值为null?太多关于此问题的误导,本文将经过实例,深化JVM剖析“目标不再运用时赋值为null”这一操作存在的含义,供君参阅。本文尽量不运用专业术语,但仍需求你对JVM有一些概念。

# 示例代码

咱们来看看一段十分简略的代码:

public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc();}

咱们在if中实例化了一个数组placeHolder,然后在if的效果域外经过System.g面试题:Java目标不再运用时,为什么要赋值为null?c();手动触发了GC,其意图是收回placeHolder,由于placeHolder现已无法拜访到了。来看看输出:

65536[GC面试题:Java目标不再运用时,为什么要赋值为null? 68239K->65952K(125952K), 0.0014820 secs][Full GC 65952K->65881K(125952K), 0.0093860 secs]

Full GC 65952K->65881K(125952K)代表的意思是:本次GC后,内存占用从65952K降到了65881K。意思其实是说GC没有将placeHolder收回掉,是不是难以想象?

下面来看看遵从“不运用的目标应手动赋值为null“的状况:

public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024);标签19 placeHolder = null; } System.gc();}

其输出为:

65536[GC 68239K->65952K(125952K), 0.0014910 secs][Full GC 65952K->345K(125952K), 0.0099610 secs]

这次GC后内存占标签17用下降到了345K,即placeHolder被成功收回了!比照两段代码,只是将placeHolder赋值为null就处理了GC的问题,真应该感谢“不运用的目标应手动赋值为null“。

等等,为什么比方里placeHolder不赋值为null,GC就“发现不了”placeHolder该收回呢?这才是问题的关键地点。

# 运行时栈

典型的运行时栈

假如你了解过编译原理,或许程序履行的底层机制,你会知道办法在履行的时分,办法里的变量(局部变量)都是分配在栈上的;当然,关于Java来说,new出来的目标是在堆中,但栈中也会有这个目标的指针,和int相同。

比方关于下面这段代码:

public static void main(String[] args) { int a = 1; int b = 2; int c = a + b;}

其运行时栈的状况能够了解成:

| 索引 | 变量 |
| :-: | :-: |
| 1 | a |
| 2 | b |
| 3 | c |

“索引”表明变量在栈中的序号,依据办法内代码履行的先后次序,变量被按次序放在栈中。

再比方:

public static void main(String[] args) { if (true) { int a = 1; int b = 2; int c = a + b; } int d = 4;}

这时运行时栈便是:

| 索引 | 变量 |
| :-: | :-: |
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |

简略了解吧?其实细心想想上面这个比方的运行时栈是有优化空间的。

Java的栈优化

上面的比方,main()办法运行时占用了4个栈索引空间,但实际上不需求占用这么多。当if履行完后,变量a、b和c都不可能再拜访到了,所以它们占用的1~3的栈索引是能够“收回”掉的,比方像这样:

| 索引 | 变面试题:Java目标不再运用时,为什么要赋值为null?量 |
| :-: | :-: |
| 1 | a |
| 2 | b |
| 3 | c |
| 1 | d |

变量d重用了变量a的栈索引,这样就节省了内存空间。

提示

上面的“运行时栈”和“索引”是为便利引进而成心创造的词,实际上在JVM中,它们的姓名别离叫做“局部变量表”和“Slot”。并且局部变量表在编译时即已确认,不需求比及“运行时”。还请留意

# GC一瞥

这儿来简略讲讲干流GC里十分简略的一小块:怎样确认目标能够被收回。另一种表达是,怎样确认目标是存活的。

细心想想,Java的国际中,目标与目标之间是存在相关的,咱们能够从一个目标拜访到另一个目标。如图所示。

再细心想想,这些目标与目标之间构成的引证联络,就像是一张大大的标签3图;更清楚一点,是很多的树。

假如咱们找到了全部的树根,那么从树根走下去就能找到全部存活的目标,那么那些没有找到的目标,便是现已逝世的了!这样GC就能够把那些目标收回掉了。

现在的问题是,怎样找到树根呢?JVM早有规矩,其间一个便是:栈中引证的目标。也便是说,只需堆中的这个目标,在栈中还存在引证,就会被认定是存活的。

提示

上面介绍的确认目标能够被收回的算法,其姓名是“可达性剖析算法”。

# JVM的标签11“bug”

咱们再来回头看看最开端的比方:

public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc();}

看看其运行时栈:

LocalVariableTable:Start Length Slot Name Signature 0 21 0 args [Ljava/lang/String; 5 12 1 placeHolder [B

栈中第一个索引是办法传入参数args,其类型为String[];第二个索引是placeHolder,其类型为byte[]。

联络前面的内容,咱们揣度placeHolder没有被收回的原因:System.gc();触发GC时,main()办法的运行时栈中,还存在有对args和placeHolder的引证,GC判别这两个目标都是存活的,不进行收回。也便是说,代码在脱离if后,尽管现已脱离了placeHolder的效果域,但在此之后,没有任何对运行时栈的读写,placeHolder地点的索引还没有被其他变量重用,所以GC判别其为存活。

为了验证这一揣度,咱们在System.gc();之前再声明一个变量,依照之前说到的“Java面试题:Java目标不再运用时,为什么要赋值为null?的栈优化”,这个变量会重用placeHolder的索引。

public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } int replacer = 1; System.gc();}

看看其运行时栈:

LocalVariableTable:Start Length Slot Name Signature 0 23 0 args [Ljava/lang/String; 5 12 1 placeHolder [B 19 4 1 replacer I

果然如此,replacer重用了placeHolder的索引。来看看GC状况:

65536[GC 68239K->65984K(125952K), 0.0011620 secs][Full GC 65984K->345K(125952K), 0.0095220 secs]

placeHolder被成功收回了!咱们的揣度也被验证了。

再从运行时栈来看,加上int replacer = 1;和将placeHolder赋值为null起到了相同的效果:断开堆中placeHolder和栈的联络,让GC判别placeHolder现已逝世。

现在算面试题:Java目标不再运用时,为什么要赋值为null?是理清了“不运用的目标应手动赋值为null“的原理了,全部本源都是来自于JVM的一个“bug”:代码脱离变量效果域时,并不会主动堵截其与堆的联络。为什么这个“bug”一向存在?你不觉得呈现这种状况的概率太小了么?算是一个tradeoff了。

# 总结

期望看到这儿你现已了解了“不运用的目标应手动赋值为null“这句话背面的奥义。我比较附和《深化了解Java虚拟机》作者的观念:在需求“不运用的目标应手动赋值为null“时斗胆去用,但不应当对其有过多依靠,更不能当作是一个遍及规矩来推面试题:Java目标不再运用时,为什么要赋值为null?广。

Write a Comment

电子邮件地址不会被公开。 必填项已用 *标注