Статьи

Перехват памяти Метод Enum.values ​​()

Я большой поклонник перечисления Java. Казалось, что мы ждали его вечно, но когда мы наконец его получили ( J2SE 5 ), enum был настолько лучше, чем предоставляемый C и C ++, что мне показалось, что «оно того стоит ». Как бы хорошо ни было enum Java, оно не без проблем. В частности, метод values() перечисления Java enum возвращает новую копию массива, представляющую его возможные значения каждый раз, когда он вызывается.

Спецификация языка Java разъясняет поведение перечисления. В спецификации языка Java Java SE 10 Edition раздел 8.9 охватывает перечисления. В разделе 8.9.3 («Члены Enum») перечислены два «неявно объявленных метода»: public static E[] values() и public static E valueOf(String name) . Пример 8.9.3-1Перебор констант перечисления с помощью Enhanced for Loop») демонстрирует вызов Enum.values() для перебора перечисления. Проблема, однако, заключается в том, что Enum.values() возвращает массив, а массивы в Java изменчивы [ Раздел 10.9 («Массив символов не является строкой») спецификации языка Java напоминает нам об этом при разграничении между Java string и массив символов Java. Перечисления Java тесно неизменны, поэтому имеет смысл, что перечисление должно возвращать клон массива, возвращаемого методом values() каждый раз, когда вызывается этот метод, чтобы гарантировать, что массив, связанный с перечислением, не изменяется.

В недавней публикации в списке рассылки OpenJDK compiler-dev под названием « о распределении памяти в Enum.values ​​() » отмечается, что « Enum.values() выделяет значительный объем памяти при вызове в узком цикле, поскольку клонирует массив постоянных значений. » Афиша этого послания добавляет, что это «вероятно для неизменности» и заявляет: «Я могу это понять». Это сообщение также ссылается на сообщение за март 2012 года и связанную ветку в этом же списке рассылки.

Два потока в списке рассылки compiler-dev содержат несколько интересных в настоящее время доступных способов решения этой проблемы.

Сообщение Брайана Гетца в этой теме начинается с предложения: «По сути, это ошибка проектирования API; поскольку values ​​() возвращает массив, а массивы изменчивы, он должен каждый раз копировать массив ». [Гетц также дразнит идею « замороженных массивов » (Java-массивы, сделанные неизменяемыми) в этом сообщении.]

Этот вопрос не новый. В публикации Уильяма Шилдса за декабрь 2009 года « Изменчивость, массивы и стоимость временных объектов в Java » говорится: «Большая проблема во всем этом заключается в том, что массивы Java являются изменяемыми». Шилдс объясняет старые и общеизвестные проблемы изменчивости в классе Java Date, прежде чем писать о конкретной проблеме, представленной b Enum.values() :

Перечисления Java имеют статический метод values() который возвращает массив всех экземпляров этого enum . После уроков класса « Date » это конкретное решение было просто шокирующим. List был бы гораздо более разумным выбором. Внутренне это означает, что массив экземпляров должен копироваться с защитой каждый раз, когда он вызывается …

Другие ссылки на эту проблему включают « метод Enums.values ​​() » (поток Guava) и « скрытые выделения в Java Enum.values ​​() » (показывает кэширование массива, возвращаемого Enum.values() ). Существует также ошибка JDK, написанная на этом: JDK-8073381 («нужен API для получения значений enum без создания нового массива»).

Некоторые из доступных в настоящее время обходных путей, обсуждаемых в этом посте, проиллюстрированы в следующем листинге кода, который представляет собой простое перечисление Fruit , демонстрирующее кэширование значений перечисления в трех различных форматах.

Перечисление Fruit.java с тремя кэшированными наборами «значений»

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package dustin.examples.enums;
 
import java.util.EnumSet;
import java.util.List;
 
/**
 * Fruit enum that demonstrates some currently available
 * approaches for caching an enum's values so that a new
 * copy of those values does not need to be instantiated
 * each time .values() is called.
 */
public enum Fruit
{
   APPLE("Apple"),
   APRICOT("Apricot"),
   BANANA("Banana"),
   BLACKBERRY("Blackberry"),
   BLUEBERRY("Blueberry"),
   BOYSENBERRY("Boysenberry"),
   CANTALOUPE("Cantaloupe"),
   CHERRY("Cherry"),
   CRANBERRY("Cranberry"),
   GRAPE("Grape"),
   GRAPEFRUIT("Grapefruit"),
   GUAVA("Guava"),
   HONEYDEW("Honeydew"),
   KIWI("Kiwi"),
   KUMQUAT("Kumquat"),
   LEMON("Lemon"),
   LIME("Lime"),
   MANGO("Mango"),
   ORANGE("Orange"),
   PAPAYA("Papaya"),
   PEACH("Peach"),
   PEAR("Pear"),
   PLUM("Plum"),
   RASPBERRY("Raspberry"),
   STRAWBERRY("Strawberry"),
   TANGERINE("Tangerine"),
   WATERMELON("Watermelon");
 
   private String fruitName;
 
   Fruit(final String newFruitName)
   {
      fruitName = newFruitName;
   }
 
   /** Cached fruits in immutable list. */
   private static final List<Fruit> cachedFruitsList = List.of(Fruit.values());
 
   /** Cached fruits in EnumSet. */
   private static final EnumSet<Fruit> cachedFruitsEnumSet = EnumSet.allOf(Fruit.class);
 
   /** Cached fruits in original array form. */
   private static final Fruit[] cachedFruits = Fruit.values();
 
   public static List<Fruit> cachedListValues()
   {
      return cachedFruitsList;
   }
 
   public static EnumSet<Fruit> cachedEnumSetValues()
   {
      return cachedFruitsEnumSet;
   }
 
   public static Fruit[] cachedArrayValues()
   {
      return cachedFruits;
   }
}

Тот факт, что Enum.values() должен клонировать свой массив при каждом вызове, во многих ситуациях не имеет большого значения. Тем не Enum.values() нетрудно представить случаи, когда было бы полезно Enum.values() вызывать Enum.values() в «узком цикле», а затем копирование значений перечисления в массив каждый раз начинало бы оказывать заметное влияние на память. используется и вопросы, связанные с большим использованием памяти. Было бы неплохо иметь стандартный подход к доступу к значениям перечисления более эффективным способом памяти. В двух упомянутых ранее потоках обсуждаются некоторые идеи для потенциальной реализации этой возможности.

Опубликовано на Java Code Geeks с разрешения Дастина Маркса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Memory-Hogging Enum.values ​​() Метод

Мнения, высказанные участниками Java Code Geeks, являются их собственными.