我应该将Java对象设置为Null吗?

4

假设我正在迭代一个对象数组,即

for (int r = 0; r < rows; r++) {
        for (int c = 0; c < columns; c++) {

            if (bricks[r][c] != null) {
                bricks[r][c].draw(gl)

如果我想在某个时候销毁这个砖块,我的remove()方法是否应该将对象设为null,例如:

private void remove(GameBrick obj) {
     for (int r = 0; r < rows; r++) {
        for (int c = 0; c < columns; c++) {
        if (bricks[r][c] != null){
        if (bricks[r][c] == obj) {
            bricks[r][c] = null;
        } 

或者它应该设置一个名为exists的标志为false,在迭代对象时,如果bricks[r][c].exists == false,则添加一个returncontinue语句?

目前我的代码基于空值对象和空检查,但我后来了解到垃圾收集器并且将对象设置为null会使其更频繁地运行。

我想知道这是否正确,并且如果需要删除对象,我应该怎么做才能最好地实现它。


1
如果你选择后者,它们就不会被垃圾回收。 - Alexander
7个回答

4
但我后来了解到垃圾回收器,将对象设置为null会使它更频繁地运行。

不,那只是道听途说。最好的方法是假设GC已经被优化为尽可能经常运行,以获得性能和内存使用之间的最佳平衡。

将引用设置为null是您向GC发出不再需要该对象的信号。GC不必立即对此做出任何反应。

更新

要调整应用程序的性能,您必须测量整个应用程序的行为-这意味着您必须首先编写整个应用程序(或非常逼真的端到端模型)。微观优化无效。

因此,最好的方法是让GC执行其设计用途-通过自动内存管理使您编写清晰,简单,易于修改的代码变得容易。这样,当您在目标机器/设备上测试应用程序并可以看到需要调整性能的位置时,很容易进行必要的更改而不会破坏任何内容。

性能优化必须受到测量的驱动。必须在完整产品的逼真原型上进行测量。因此,在第一次实现中,专注于编写易于修改的代码。然后测量,并仅在实际需要它们的地方放置混乱的hack。

请记住,它们可能需要放置在不同的位置,具体取决于您正在运行的设备!在某些设备上,在特定位置应用的hack可能会使您变慢,而在另一个设备上,它会加速您。因此,您不能仅仅盲目遵循所有代码中的规则。您必须进行测量。


2
最好的方法是假设垃圾回收已经被优化,以便尽可能频繁地运行,以获得性能和内存使用之间的最佳平衡。这是一个安全的假设吗? - edthethird
这是一个游戏,你不希望GC在游戏循环中运行(尽管有时是不可避免的),特别是在Android中,因为Dalvik GC使用了停止-全局标记和清除。 - kabuko
快速修正,自从Gingerbread版本以后我认为它是并发的,但是仍然有很多Froyo设备存在。 - kabuko
将其设置为null不会从内存中删除它,但它会缩短GC运行时所需的时间,还是延长它的时间? - user717572
1
@edthethird - 这基本上是垃圾回收的整个目的,来照顾这些事情。如果你不信任它,为什么要使用它呢?相反,使用本地代码(例如在Android上,使用NDK)来执行更多性能关键的工作。 - Daniel Earwicker
@user717572 - 那个问题的答案取决于大量因素,一般来说无法回答。 - Daniel Earwicker

3
我后来了解到垃圾回收器,将对象设置为null会让它更频繁地运行,这并不是正确的。垃圾回收器会按计划定期运行,并在JVM内存耗尽时运行。通过将引用设置为null,您只能增加GC释放的内存量,并减少其工作量,因为Java中使用的GC类型是O(|non-garbage-memory|)。
增加释放的内存量可以降低 O(|non-garbage-memory|), 从而使JVM更少运行出现内存不足的情况,并且对于定期运行没有影响。
当不再需要对象的引用时,请将其设置为null。编写函数时,让那些耗时的函数尽可能少地传递参数。构造您的类,以使它们松散耦合--其结果之一是长寿命的对象具有较少的成员。保持一致,并且您就处于JVM实现者已经优化的状态。

请注意,这是一个游戏,并且是在Android上运行的,因此它不使用标准JVM,而是Dalvik。 - kabuko
@kabuko,我明白。Dalvik VM也使用标记-清除GC(尽管不是压缩型),因此相同的论点适用,除了关于O(|non-garbage-memory|)的部分。 - Mike Samuel
@kabuko,在Dalvik中,我不认为有一个常规时间表。在Oracle的JVM中,请参见“调整垃圾回收”(http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html#0.0.0.%20The%20Concurrent%20Low%20Pause%20Collector%7Coutline),其中指出:“使用RMI的应用程序引用其他虚拟机中的对象。在这些分布式应用程序中不能进行垃圾回收而不进行偶尔的本地收集,因此RMI强制执行周期性的完全收集。这些收集的频率可以通过属性进行控制。” - Mike Samuel
这就是为什么我在我的第一条评论中提到了Dalvik。:) 虽然谢谢你提供的链接。我之前看过那个文档,但应该有时间完整地阅读一遍。 - kabuko
@kabuko,有点偏题:个人而言,我希望看到Java允许重载new运算符,尽管我通常对没有相应契约的运算符重载持怀疑态度。 - Mike Samuel
显示剩余2条评论

2

在游戏中,您希望尽可能减少GC时间。不要将它们设置为null,而是创建一个池,标记死亡的对象,而且不要实例化新对象,而是从池中选择一个死亡的对象并将其恢复。


1

是的,将引用设置为null。这将导致垃圾回收器为您的应用程序释放更多的内存。将引用设置为null不会导致垃圾回收器更频繁地运行,但它肯定会帮助它释放更多的内存。


1

答案将取决于您的特定代码,但通常对于安卓游戏而言,您要尽可能避免大量垃圾回收和对象创建(请参见这里了解一些细节)。

您知道砖块的最大行数和列数吗?您会经常销毁和创建砖块吗?如果是这样,最好预先分配所有砖块,并使用布尔值指示砖块是否存活。


0

考虑在将砖块附加到“bricks”容器时,将位置r/c存储在砖块内部。然后您就不必搜索,只需查看砖块内部即可。这假设您最多可以将砖块添加一次到容器中。

确保在包含对象的生命周期长于代码范围的所有情况下将引用设置为null。无需设置局部变量为null,这将在作用域结束时(块"}"的结尾)隐式完成。


0
首先,直接回答你的问题:将该引用设置为null并没有本质上的错误。请注意,你并没有真正将对象设置为null,而是将指向该对象的引用设置为null(或者如果你想要严谨一些,可以说是将指向该对象的引用的值设置为null) 在你的特定情况下,这是否是一个好主意则是另一回事。你当然可以通过不分配或释放对象来最小化垃圾收集(参见这个Google I/O演示)。我很想看到其他人提供相反证据的观点,但他们似乎不同意。如果你的2D数组是对砖块的唯一引用,那么将其设置为null将会取消对该对象的引用。虽然取消对对象的引用可能不会使GC更频繁地运行(我不确定这一点,尽管我所读过的所有文献都建议在Android游戏循环中避免取消引用和分配),但分配肯定会。
如果你的砖块总是处于相同的位置,并且它们只是被摧毁了(就像Breakout/Arkanoid一样),那么你的布尔标志想法可能是一个好主意。在这种情况下,你的2D数组有点像一个固定大小的池。
如果你的砖块可以改变位置,那么在移动时你可以做同样的事情并交换砖块,但有时候直接进行常规的添加/删除会更加自然。这种情况下,你应该提前在池中分配砖块,然后可以在你的二维数组中将引用设置为null,而不必担心垃圾回收,因为你的池仍然持有一个引用(但你需要先将其释放回池中)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接