【问题标题】:Possible heap pollution via varargs parameter通过 varargs 参数可能造成的堆污染
【发布时间】:2012-09-09 20:03:31
【问题描述】:

我了解 Java 7 在使用泛型类型的可变参数时会发生这种情况;

但我的问题是..

当 Eclipse 说“它的使用可能会污染堆”时,究竟是什么意思?

还有

新的@SafeVarargs 注释如何防止这种情况发生?

【问题讨论】:

标签: java eclipse generics variadic-functions


【解决方案1】:

堆污染是一个技术术语。它指的是类型不是它们指向的对象的超类型的引用。

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

这可能导致“无法解释”ClassCastExceptions。

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargs 根本不会阻止这一点。但是,有些方法可以证明不会污染堆,编译器无法证明。以前,此类 API 的调用者会收到令人讨厌的警告,这些警告完全没有意义,但必须在每个调用站点都被抑制。现在 API 作者可以在声明站点将其禁止一次。

但是,如果该方法实际上是安全的,用户将不再被警告。

【讨论】:

  • 那么我们是说堆被污染是因为它包含的引用类型不是我们所期望的吗? (您的示例中的 List 与 List
  • 这个答案很好地解释了堆污染是什么,但它并没有真正解释为什么可变参数特别容易导致它以保证特定的警告。
  • 我也是,我缺少如何确保我的代码不包含此问题的信息(例如,我怎么知道它已经足够硬化以添加 @SafeVarargs)
【解决方案2】:

当你声明时

public static &lt;T&gt; void foo(List&lt;T&gt;... bar) 编译器将其转换为

public static &lt;T&gt; void foo(List&lt;T&gt;[] bar) 然后到

public static void foo(List[] bar)

这样就会出现危险,即您会错误地将不正确的值分配到列表中,而编译器不会触发任何错误。例如,如果TString,那么下面的代码将编译没有错误,但在运行时会失败:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

如果您检查了该方法以确保它不包含此类漏洞,那么您可以使用 @SafeVarargs 对其进行注释以抑制警告。对于接口,使用@SuppressWarnings("unchecked")

如果您收到此错误消息:

Varargs 方法可能会导致不可具体化的 varargs 参数造成堆污染

并且您确定您的使用是安全的,那么您应该改用@SuppressWarnings("varargs")。请参阅 Is @SafeVarargs an appropriate annotation for this method?https://stackoverflow.com/a/14252221/14731 以获得对第二种错误的很好解释。

参考资料:

【讨论】:

  • 我想我理解得更好了。当您将 varargs 转换为 Object[] 时,危险就来了。只要你不投到Object[],听起来应该没问题。
  • 作为一个愚蠢的例子,你可以做:static &lt;T&gt; void bar(T...args) { ((Object[])args)[0] = "a"; }。然后拨打bar(Arrays.asList(1,2));
  • @djeikyb 如果只有在我转换为Object[] 时才会出现危险,如果我不这样做,为什么编译器会触发警告?毕竟,在编译时检查它应该相当容易(如果我不将它传递给具有类似签名的另一个函数,在这种情况下,另一个函数应该触发警告)。我不相信这真的是警告的核心(“如果你不施放,你是安全的”),我仍然不明白在哪种情况下我很好。
  • @djeikyb 如果没有参数化的可变参数(例如bar(Integer...args)),您可能会做同样愚蠢的事情。那么这个警告有什么意义呢?
  • @VasiliyVlasov 此问题仅与参数化可变参数有关。如果您尝试对非类型数组执行相同的操作,运行时将阻止您将错误的类型插入到数组中。编译器警告您,运行时将无法防止不正确的行为,因为参数类型在运行时未知(相比之下,数组确实在运行时知道其非泛型元素的类型)。
【解决方案3】:

@SafeVarargs 不会阻止它的发生,但是它要求编译器在编译使用它的代码时更加严格。

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html 更详细地解释了这一点。

堆污染是当您在泛型接口上执行操作时收到ClassCastException,并且它包含不同于声明的类型。

【讨论】:

  • 编译器对其使用的额外限制似乎并不特别相关。
【解决方案4】:

当您使用可变参数时,可能会创建一个Object[] 来保存参数。

由于逃逸分析,JIT 可以优化掉这个数组创建。 (我发现它是少数几次之一)它不能保证被优化掉,但除非你在内存分析器中看到它的问题,否则我不会担心它。

AFAIK @SafeVarargs 禁止编译器发出警告,并且不会改变 JIT 的行为方式。

【讨论】:

  • 有趣,虽然它并没有真正回答他关于@SafeVarargs的问题。
  • 不。这不是堆污染。 “当参数化类型的变量引用不属于该参数化类型的对象时,就会发生堆污染。”参考:docs.oracle.com/javase/tutorial/java/generics/…
【解决方案5】:

原因是可变参数提供了使用非参数化对象数组调用的选项。因此,如果您的类型是 List ... ,也可以使用 List[] 非可变参数类型调用它。

这是一个例子:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

如您所见,List[] b 可以包含任何类型的消费者,但这段代码可以编译。如果你使用可变参数,那么你很好,但如果你在类型擦除之后使用方法定义 - void test(List[]) - 那么编译器将不会检查模板参数类型。 @SafeVarargs 将禁止显示此警告。

【讨论】:

    【解决方案6】:

    当您可以控制方法的调用方式(例如,类的私有方法)时,将 @SafeVarargs 注释添加到方法是相当安全的。您必须确保仅将声明的泛型类型的实例传递给方法。

    如果将方法作为库暴露在外部,则很难发现此类错误。在这种情况下,最好避免使用此注释,并使用集合类型(例如Collection&lt;Type1&lt;Type2&gt;&gt;)输入而不是可变参数(Type1&lt;Type2&gt;...)重写解决方案。

    至于命名,在我看来,堆污染 现象这个词相当具有误导性。在documentation 中没有提到实际的JVM heap 事件。 Software Engineering 有一个question,其中包含一些关于这种现象命名的有趣想法。

    【讨论】:

      猜你喜欢
      • 2015-09-30
      • 2016-12-11
      • 1970-01-01
      • 1970-01-01
      • 2015-05-08
      • 1970-01-01
      • 2016-10-17
      相关资源
      最近更新 更多