减少用于返回数组的方法的Java垃圾回收?

时间:2018-10-26 20:27:21

标签: java performance garbage-collection

对象A具有方法B(),并且在应用程序的大部分生命周期内都有效。 B调用对象C的方法D()。 D()返回一个数组,该数组最多包含x个MyData对象。 MyData可能是POD(普通旧数据)/ PDS(被动数据结构),也可能更多,但是MyData可以通过调用方法或设置字段来重用;它的身份或功能在构建过程中或其他方面不会一成不变。

当前B()的定义如下:

class A {
  public B() {
    MyData[] amydata = c.D( 5 );
       :
       :
  }
}

当前D()的定义如下:

MyData[] D( int iRows ) {

    MyData[] amydata = new MyData[ iRows ];

    for ( int i = 0; i < iRows; i++ ) {

        if ( no more data )
            return amydata;

        amydata [ i ] = new MyData();

        // calculate or fill in MyData structure.
    }

    return amydata;
}

即使数据会有所不同,A也会一直或很长时间(例如,直到用户重新配置它)要求相同的行数。

那如果我在数组引用中有调用者传递呢?

class A {
  int iRequestSize = 5;
  int iResultSize;
  MyData[] amydata = new MyData[ iRequestSize ];

  public B() {
    iResultSize = c.D( iRequestSize, amydata );
       :
       :
    // use up to iResultSize even though array is potentially bigger.
  }
}

// returns number of rows actually used
int D( int iRowsMax, MyData[] amydata ) {

    for ( int i = 0; i < iRowsMax; i++ ) {

        if ( no more data )
            return i;

        if ( amydata [ i ] == null )
            amydata [ i ] = new MyData();

        // calculate or fill in MyData structure.
    }

    return iRowsMax;
}

我是C ++的人,刚接触Java,但似乎假设MyData可以像这样被回收,第二个版本应该避免创建和复制MyData并避免垃圾回收?

2 个答案:

答案 0 :(得分:2)

我想说第二种情况更糟。

在第一个变体amydata中,一旦方法B()退出(假设B不存储对amydata的引用,就可以对其进行所有引用的对象进行垃圾回收。其他地方。

在第二个变体amydata中,只要A的实例存在就无法进行垃圾收集。

请考虑以下情况:在第一次调用D()时,它返回5个对MyData对象的引用,但是在后续调用中,它不返回任何行。在第一种变体中,amydata返回时可以立即垃圾回收MyData数组和5个引用的B()对象。但是在第二种变体中,amydata数组和通过它引用的5个MyData对象都不能被垃圾回收-可能永远不会在应用程序的整个运行期间。

请记住:Java垃圾收集器针对许多短期对象进行了优化

答案 1 :(得分:1)

免责声明: 阅读OP的评论,我不得不承认我没有得到他的真正意图,即开发软实时应用程序,尽可能避免垃圾回收,这在Java世界中是非常特殊且罕见的情况。

因此以下答案与他的问题不符。但是,由于偶然的读者从C ++迁移到Java可能会迷惑于此问题和答案,因此他/她可能会获得一些有关典型Java编程风格的有用提示。


尽管Java和C ++的语法有很多相似之处,但是由于运行时环境差异很大,您应该采用不同的编码样式。

作为一个拥有数十年Java经验的人,我肯定会喜欢原始的方法签名。作为D()方法的调用者,为什么我应该创建结果数据结构而不是从正在调用的方法中获取它?这会逆转自然的数据流。

我知道,在过去的C时代,动态内存管理意味着很多麻烦,在函数外部准备结果数组并让函数仅填充结果是很常见的,这是您编写第二个版本的方式。但是,使用Java则无需再理会了,只需让垃圾收集器完成其工作即可(并且在这项工作中非常出色)。通常,尝试“帮助” GC会导致实际上效率较低且难以阅读的代码。而且,如果您真的想坚持这种风格,则无需同时传递最大行数和数组,因为数组本身知道其长度(与旧式C数组不同),给出了最大行数。 / p>

您假设

  

第二个版本应避免创建和复制MyData的

这听起来像是对Java内部工作的误解。每次执行new MyData(...)表达式时,都会在堆上的某个位置创建一个新实例。提供MyData[]数组并不能避免这种情况。转换为C术语后,该数组仅保存指向MyData对象而不是实际对象的指针。而且几乎不会复制Java实例(除非您显式调用类似object.clone()之类的东西)。将实例分配给变量时,它只是复制实例的引用(=指针)。

但是,如果我正确理解其目的,即使第一个版本也不是完美的。 D()方法本身可以确定何时没有更多数据可用,那么为什么返回数组的时间比必要的时间长呢?使用Java数组有点不方便,因此典型的Java代码在类似情况下会返回List<MyData>

关于MyData()构造函数的另一条注释,后来又“计算或填写MyData结构”。我知道样式存在(并且在C语言家族中非常流行),但是在Java中它并不占主导地位,我尤其不喜欢它。对我来说,这听起来像是在问“给我一辆汽车”,只是得到一个骨架而不是一辆可用的汽车。如果我要有轮子,引擎和座椅,我以后必须自己提供。如果可用的汽车需要选择选项,那么我想在订购汽车时/调用构造函数时提供它们,这样我就可以诚实地将结果称为汽车而不是骨架。

最后是对Java命名约定的评论:绝大多数Java代码都遵循这些约定,因此您以大写字母开头的方法名称对我来说很奇怪。