Могут ли "финальные" классы в Java быть распределены по-разному в некоторых JVM? - PullRequest
3 голосов
/ 16 августа 2011

Есть ли у кого-нибудь информация о том, какие оптимизации (я понимаю, что это зависит от реализации) большинство JVM будет делать, когда сталкивается с конечным объектом? В частности, массив конечных объектов в Java? Например:

final class A { }
A myArray[] = new A[10];

Если класс "A" является окончательным, то он не может иметь подклассов, поэтому кажется, что можно было бы выделить (не вызывая конструкторов) весь массив (т.е. malloc (sizeof (A) * 10)) и сохранить по вывозу мусора / бухгалтерскому учету.

Ответы [ 4 ]

2 голосов
/ 16 августа 2011

Я сильно сомневаюсь, что любая JVM потрудится сделать это. Частично потому, что это, вероятно, создаст дополнительный бухгалтерский учет. Любой экземпляр, хранящийся как в массиве, так и в другом месте, где необходимо выполнить двойную модификацию, чтобы соответствовать правилам JVM.

например.

final class A {
    String value = "default";
}

A instance = new A();
A[] array = new A[] {instance};
instance.value = "another value";
assert instance == array[0];
assert instance.value == array[0].value;

Реализация, которую вы предлагаете, приведет к созданию копии instance при создании массива, что заставит JVM не забывать изменять содержимое value всякий раз, когда оно было изменено в другом экземпляре A.


В ответ на ваш комментарий:

Это интересная идея и будет работать в определенной степени. Однако ваша идея подорвет производительность сборщика мусора. Причина этого в том, что лучшие JVM не используют подсчет ссылок. Лучшие JVM используют движущийся сборщик мусора . Этот метод отслеживает все корневые узлы (например, потоки), чтобы увидеть, на какие объекты ссылаются. Все объекты в цепочке ссылок перемещаются в непрерывный блок. Любая память за пределами этого блока считается свободной. Никаких звонков на dealloc или на доработку или еще что-нибудь. Этот метод ОЧЕНЬ быстр (частично из-за высокой "младенческой смертности" объектов на языках GCed). Более того, этот метод не должен проверять циклические ссылки.

Вернемся к вопросу: когда массив выходит из области видимости, JVM должна проверить, есть ли какие-либо другие ссылки на элементы массива, и распределить новое пространство для этих объектов, прежде чем освободить память массива. Это предотвращает использование «движущегося сборщика мусора», и мы должны вернуться к неэффективным методам GC, таким как подсчет ссылок. Таким образом, хотя ваша идея на первый взгляд кажется хорошей (и только для определенного крайнего случая), она не позволяет использовать другие стратегии GC, которые могут применяться более широко, и обеспечивает гораздо большую экономию эффективности.

1 голос
/ 16 августа 2011

Когда вы создаете массив в Java. экземпляры элементов не создаются! Экземпляр java никогда не является C-подобной структурой, которая копируется или выделяется для массива (массив содержит только ссылки). Поэтому нет разницы, является ли тип элемента конечным или нет.

* После 1003 *

A[] myArray = new A[10];

myArray[n] всегда null! После этого вы можете назначить элементы:

myArray[0] = new A();
1 голос
/ 16 августа 2011

Оптимизация для окончательного ключевого слова, которую JVM (или более точный компилятор) делает по-разному для разных случаев.

  1. Как правило, хорошей практикой кодирования является объявление всех переменных метода какфайл (если вы не меняете их внутри метода).Теперь компилятор (не jvm) заменяет все вхождения этой переменной ее значением (в качестве окончательного значения она не изменится).

  2. Для myArray[] = new A[10]; ссылка на массив является окончательной, а не значения массива.

  3. Для final class A { } это просто ограничение проектапри этом класс не имеет возможности создавать подклассы.

    Итак, глядя на это, кажется, что оптимизация для final выполняется главным образом компилятором и, следовательно, должна быть одинаковой во всех реализациях.

0 голосов
/ 19 августа 2011

Я читал о последних методах сегодня. И кажется, что объявление финала класса - это всего лишь сокращение для объявления всех методов финальными.

Преимущество объявления метода final состоит в том, что он позволяет компилятору избегать использования поиска в виртуальной таблице при отправке метода, то есть он точно знает, в каком классе находится метод.

В Java методы по умолчанию являются виртуальными, тогда как в C ++ верно обратное. Единственные методы, которые не являются виртуальными в Java, - это статические методы и конечные методы.

Выигрыш в производительности от явного объявления метода final является спорным. Хороший JIT-компилятор сможет анализировать код и выполнять любые оптимизации, которые он сочтет полезными. Если класс A в настоящее время не имеет загруженных подклассов, то JIT-компилятор может решить оптимизировать любые пути кода, которые вызывают методы экземпляра A, если они вызываются часто. Это включает в себя избавление от поиска в vtable или даже вставку вызова метода.

например. следующий путь кода является хорошим кандидатом для оптимизации, если метод выполняется быстро.

public void run(A instance) {
    while (true) {
        instance.method();
    }
}

Если впоследствии загружается подкласс A, который переопределяет метод, то JIT-компилятор может лишить законной силы любые оптимизации, сделанные в отношении вызова метода.

По сути, хороший JIT может дать все преимущества объявления чего-то окончательного, без необходимости объявления чего-либо окончательного. И поскольку нам не нужно объявлять что-либо окончательное, мы можем разработать полностью объектно-ориентированный подход, не беспокоясь о том, является ли определенный класс или метод конечным. Следовательно, final должен использоваться только для целей документирования. Единственное исключение - это поля класса, поскольку он помечает любые вычисления с этими полями как кандидатов на запоминание - то есть кэширует результат и использует кэшированный результат, а не выполняет вычисление снова.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...