it-swarm.com.ru

Потенциальное загрязнение кучи через параметр varargs

Я понимаю, что это происходит с Java 7 при использовании varargs с универсальным типом;

Но мой вопрос ..

Что именно означает «Затмение», когда говорит, что «его использование может привести к загрязнению кучи?»

А также 

Как новая аннотация @SafeVarargs предотвращает это?

377
hertzsprung

Загрязнение кучи - технический термин. Это относится к ссылкам, которые имеют тип, который не является супертипом объекта, на который они указывают.

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 может отключить его один раз на сайте декларации.

Однако, если метод на самом деле является не безопасным, пользователи больше не будут предупреждены.

219
Ben Schulz

Когда вы объявляете

public static <T> void foo(List<T>... bar) компилятор преобразует его в

public static <T> void foo(List<T>[] bar) тогда к

public static <T> void foo(List[] bar)

Тогда возникает опасность, что вы ошибочно назначите неверные значения в список, и компилятор не вызовет никаких ошибок. Например, если T является String, следующий код скомпилируется без ошибок, но завершится с ошибкой во время выполнения:

// 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") вместо этого. Смотрите Является ли @SafeVarargs подходящей аннотацией для этого метода? и https://stackoverflow.com/a/14252221/14731 для хорошего объяснения этого второго вида ошибки.

Рекомендации:

207
Gili

@SafeVarargs не препятствует этому, однако он требует, чтобы компилятор был более строгим при компиляции кода, который его использует. 

http://docs.Oracle.com/javase/7/docs/api/Java/lang/SafeVarargs.html объясняет это более подробно.

Загрязнение кучи происходит, когда вы получаете ClassCastException при выполнении операции с универсальным интерфейсом, и он содержит другой тип, чем объявленный.

7
jontro

Когда вы используете varargs, это может привести к созданию Object[] для хранения аргументов.

Благодаря escape-анализу JIT может оптимизировать создание этого массива. (Один из немногих случаев, когда я обнаружил, что это так). Его нельзя оптимизировать, но я не буду беспокоиться об этом, если вы не обнаружите проблему в профилировщике памяти.

AFAIK @SafeVarargs подавляет предупреждение компилятором и не меняет поведение JIT.

5
Peter Lawrey

Причина в том, что varargs дают возможность вызываться с непараметризованным массивом объектов. Так что, если ваш тип был List <A> ..., он также может быть вызван с типом List [] не-varargs.

Вот пример:

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

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

Как видите, List [] b может содержать любой тип потребителя, и все же этот код компилируется. Если вы используете varargs, то у вас все хорошо, но если вы используете определение метода после стирания типа - void test (List []) - тогда компилятор не будет проверять типы параметров шаблона. @SafeVarargs отключит это предупреждение.

0
user1122069