Почему существует sun.misc.Unsafe и как его можно использовать в реальном мире? [закрыто]


267

Я наткнулся на пакет sun.misc.Unsafe на днях и был поражен тем, что он может сделать.

Конечно, класс недокументирован, но мне было интересно, была ли когда-нибудь веская причина использовать его. Какие сценарии могут возникнуть, когда вам нужно будет использовать его? Как это может быть использовано в реальном сценарии?

Кроме того, если вам это нужно, не означает ли это, что что-то не так с вашим дизайном?

Почему Java даже включает этот класс?


7
Разработчики JDK в настоящее время рассматривают этот API-интерфейс на предмет возможного преобразования в общедоступный API-интерфейс в Java 9. Если вы используете его, стоит заполнить опрос в течение 5 минут: surveymonkey.com/s/sun-misc-Unsafe .
Энди Линч

2
Этот пост обсуждается на meta: meta.stackoverflow.com/questions/299139/…
Джон Клементс

Ответы:


159

Примеры

  1. ВМ "Интрификация". т.е. CAS (Compare-And-Swap), используемый в хэш-таблицах без блокировок, например: sun.misc.Unsafe.compareAndSwapInt он может делать реальные вызовы JNI в собственный код, который содержит специальные инструкции для CAS

    Узнайте больше о CAS здесь http://en.wikipedia.org/wiki/Compare-and-swap

  2. Функциональность sun.misc.Unsafe виртуальной машины хоста может использоваться для выделения неинициализированных объектов, а затем интерпретировать вызов конструктора как любой другой вызов метода.

  3. Можно отслеживать данные по собственному адресу. Можно получить адрес памяти объекта с помощью класса java.lang.Unsafe и работать с его полями напрямую через небезопасные методы get / put!

  4. Оптимизация времени компиляции для JVM. Высокая производительность ВМ с использованием «магии», требующей низкоуровневых операций. например: http://en.wikipedia.org/wiki/Jikes_RVM

  5. Выделение памяти, sun.misc.Unsafe.allocateMemory, например: - конструктор DirectByteBuffer внутренне вызывает его, когда вызывается ByteBuffer.allocateDirect

  6. Трассировка стека вызовов и воспроизведение со значениями, созданными экземпляром sun.misc.Unsafe, полезным для инструментария

  7. sun.misc.Unsafe.arrayBaseOffset и arrayIndexScale могут быть использованы для разработки массивов, техники эффективного разбиения больших массивов на более мелкие объекты, чтобы ограничить стоимость сканирования, обновления или перемещения больших объектов в режиме реального времени.

  8. http://robaustin.wikidot.com/how-to-write-to-direct-memory-locations-in-java

больше на ссылках здесь - http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html


1
если вы получаете адрес поля с помощью Unsafe, он всегда может быть изменен с помощью GC, так разве эта операция бесполезна?
Пдева

получить адрес для тех, кого вы выделили
zudokod

что именно вы подразумеваете под тем, что я выделил. кажется, это используется в местах, где объекты были созданы с использованием оператора «new», поэтому мой вопрос.
Пдева

1
unsafe.allocateMemory и поместите значение
zudokod

1
Что касается пункта 2, я хотел бы знать, как вы можете вызвать конструктор, как любой другой вызов метода? Потому что я не нашел никакого способа сделать это, кроме как в байт-кодах.
Мигель Гамбоа

31

Просто запустив поиск в некоторой поисковой системе кода, я получаю следующие примеры:

  • Java Object Notation - используйте его для более эффективной обработки массива, цитируя Javadoc

Простой класс для получения доступа к объекту {@link Unsafe}. {@link Unsafe} * требуется для обеспечения эффективной работы CAS над массивами. Обратите внимание, что версии в {@link java.util.concurrent.atomic}, такие как {@link java.util.concurrent.atomic.AtomicLongArray}, требуют дополнительных гарантий упорядочения памяти, которые обычно не нужны в этих алгоритмах, а также являются дорогостоящими. на большинстве процессоров.

  • SoyLatte - Java 6 для выдержки из osx javadoc

/ ** Базовый класс для sun.misc.Незопасные FieldAccessors для статических полей. Наблюдение состоит в том, что с точки зрения кода отражения существует только девять типов полей: восемь примитивных типов и Object. Использование класса Unsafe вместо сгенерированных байт-кодов экономит память и время загрузки для динамически генерируемых FieldAccessors. * /

  • SpikeSource

/ * FinalFields, которые отправляются по проводам .. как разобрать и воссоздать объект на принимающей стороне? Мы не хотим вызывать конструктор, поскольку он установит значения для конечных полей. Мы должны воссоздать последнее поле в точности так, как оно было на стороне отправителя. Sun.misc.Unsafe делает это для нас. * /

Есть много других примеров, просто перейдите по ссылке выше ...


25

Интересно, что я никогда не слышал об этом классе (что, наверное, хорошо, правда).

Одна вещь, которая приходит на ум, - это использование Unsafe # setMemory для обнуления буферов, которые содержали конфиденциальную информацию в одном месте (пароли, ключи, ...). Вы могли бы даже сделать это с полями «неизменяемых» объектов (опять же, я полагаю, что и здесь может помочь простое старое отражение). Я не эксперт по безопасности, так что возьмите это с крошкой соли.


4
I'd never even heard of this class... Я говорил вам об этом много раз! вздох + :(
Тим Бендер

7
Не было бы никакого смысла, так как Java использует копирующий сборщик мусора поколений, и ваша конфиденциальная информация, вероятно, уже будет находиться где-то еще в «свободной» памяти, ожидая перезаписи.
Дэниел Кэссиди

39
Я никогда об этом не слышал, но мне нравится их park()документация: «Заблокировать текущий поток, возвращающийся, когда происходит разблокировка балансировки, или уже произошла разблокировка балансировки, или поток прерывается, или, если не абсолютное значение, а время не равно нулю, заданное время истекло наносекунды или, если оно абсолютное, заданный крайний срок в миллисекундах с тех пор, как прошла Эпоха, или с лихвой (т. е. возвращение без «причины») ». Почти так же хорошо, как «память освобождается при выходе из программы или через произвольные интервалы, в зависимости от того, что наступит раньше».
aroth

1
@ Даниэль, интересно, я не учел это. Теперь вы можете понять, почему я не эксперт по безопасности. :)
Майк Дэниелс

22

Основываясь на очень кратком анализе библиотеки Java 1.6.12 с использованием eclipse для отслеживания ссылок, кажется, что каждая полезная функциональность Unsafeраскрывается полезными способами.

Операции CAS предоставляются через классы Atomic *. Функции манипулирования памятью предоставляются через инструкции DirectByteBuffer Sync (park, unpark) и через AbstractQueuedSynchronizer, который, в свою очередь, используется реализациями Lock.


AtomicXXXUpdaters слишком медленные, и когда они вам действительно нужны: CAS - вы не можете позволить себе использовать их на самом деле. Если вы собираетесь делать металл, вы не будете использовать уровни абстракции и многочисленные проверки. Сбой CAS - это плохо в цикле ESP. когда аппаратное обеспечение решает неверно предсказать ветвь (из-за высокой конкуренции), но еще несколько сравнений / ветвей просто больно. Park / Unpark выставляются LockSupportне через AQS (последний является скорее замком, чем парковка / unpark)
bestsss

21

Unsafe.throwException - позволяет бросить проверенное исключение, не объявляя их.

Это полезно в некоторых случаях, когда вы имеете дело с рефлексией или АОП.

Предположим, вы создали универсальный прокси для пользовательского интерфейса. И пользователь может указать, какое исключение выдается имплементацией в особом случае, просто объявив исключение в интерфейсе. Тогда это единственный способ, которым я знаю, - вызвать проверенное исключение в динамической реализации интерфейса.

import org.junit.Test;
/** need to allow forbidden references! */ import sun.misc.Unsafe;

/**
 * Demonstrate how to throw an undeclared checked exception.
 * This is a hack, because it uses the forbidden Class {@link sun.misc.Unsafe}.
 */
public class ExceptionTest {

    /**
     * A checked exception.
     */
    public static class MyException extends Exception {
        private static final long serialVersionUID = 5960664994726581924L;
    }

    /**
     * Throw the Exception.
     */
    @SuppressWarnings("restriction")
    public static void throwUndeclared() {
        getUnsafe().throwException(new MyException());
    }

    /**
     * Return an instance of {@link sun.misc.Unsafe}.
     * @return THE instance
     */
    @SuppressWarnings("restriction")
    private static Unsafe getUnsafe() {
        try {

            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);

        } catch (IllegalArgumentException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (SecurityException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (NoSuchFieldException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (IllegalAccessException e) {
            throw createExceptionForObtainingUnsafe(e);
        }
    }

    private static RuntimeException createExceptionForObtainingUnsafe(final Throwable cause) {
        return new RuntimeException("error while obtaining sun.misc.Unsafe", cause);
    }


    /**
     * scenario: test that an CheckedException {@link MyException} can be thrown
     * from an method that not declare it.
     */
    @Test(expected = MyException.class)
    public void testUnsingUnsaveToThrowCheckedException() {
        throwUndeclared();
    }
}

14
вы можете делать то же самое Thread.stop(Throwable)без необходимости в небезопасных, в том же потоке вы можете бросить что угодно в любом случае (нет проверки компиляции)
bestsss

Вы можете сделать это только через байт-код (или используйте Lomboc, чтобы сделать это за вас)
Сурьма,

1
@bestsss Этот метод был заглушен и добавляет UnsupportedOperationExceptionтекущий поток начиная с Java 8. Однако версия без аргументов, которая выдает, ThreadDeathвсе еще работает.
gparyani

@damryfbfnetsi, я давно не слежу за обсуждениями ядра jdk и не планирую переходить на java 8. Тем не менее, это довольно загадочная идея, поскольку в любом случае тривиально реализовать генерацию байт-кода, если только теперь верификатор не проверяет, действительно ли они Метод объявляет throwables ... но это может быть обратно несовместимо, поскольку метаданные о сгенерированном исключении могут быть отброшены.
bestsss

10

Класс небезопасный

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

Одно использование этого в java.util.concurrent.atomicклассах:


6

Для эффективного копирования памяти (быстрее копировать, чем System.arraycopy () для коротких блоков по крайней мере); как используется Java LZF и кодеками Snappy . Они используют 'getLong' и 'putLong', которые работают быстрее, чем делают побайтные копии; особенно эффективно при копировании таких вещей, как блоки 16/32/64 байта.


1
Doh, arraycopy использует циклы SSE на x86-64, которые лучше, чем getLong/putLong(и вы должны также рассчитать адрес)
bestsss

Вы на самом деле измерили это? Для более коротких блоков я вижу постоянно лучшую производительность на x86-64 при использовании комбинации getLong/ putLong: в идеале я бы предпочел System.arraycopy()для простоты и все; но фактическое тестирование показало обратное для случаев, которые я проверял.
StaxMan

да, используя небезопасные, я не смог бы сколько-нибудь значимого исполнения из deflate impl. Для нескольких байтов длинных копий больших массивов get / putLong может действительно работать, когда компилятор должен проверять длины. Некоторые вкл. добавить ограждение памяти за System.arrayCopy (может быть отключен / включен, хотя), чтобы это могло быть реальным преступником.
bestsss

Хорошо. Возможно, что новые JDK изменили это; Первоначально, когда я наблюдал более быструю работу (с JDK 1.6), я тоже был удивлен. Или, возможно, я забыл какую-то конкретную разницу в использовании. Это хитрые (и, возможно, нестабильные) оптимизации, даже когда они работают, и важно измерять эффекты.
StaxMan

5

Недавно я работал над реализацией JVM и обнаружил, что в плане реализовано удивительное количество классов Unsafe. Этот класс в основном предназначен для разработчиков библиотек Java и содержит функции, которые в основном небезопасны, но необходимы для создания быстрых примитивов. Например, существуют методы для получения и записи необработанных смещений полей, использующие синхронизацию на уровне оборудования, выделение и освобождение памяти и т. Д. Он не предназначен для использования обычными программистами Java; он недокументирован, специфичен для реализации и небезопасен (отсюда и название!). Более того, я считаю, что SecurityManagerдоступ к нему будет закрыт практически во всех случаях.

Короче говоря, он в основном существует, чтобы позволить разработчикам библиотек доступ к базовому компьютеру без необходимости объявлять каждый метод в определенных классах, как AtomicIntegernative. Вам не нужно использовать или беспокоиться об этом в обычном программировании на Java, поскольку весь смысл в том, чтобы сделать остальные библиотеки достаточно быстрыми, чтобы вам не требовался такой доступ.


на самом деле SecurityManager запрещает доступ к нему, только если отражение отключено
amara

@ sparkleshy- Можете ли вы уточнить это?
templatetypedef

при получении экземпляра из getUnsafe действительно имеет довольно жесткие требования, Unsafe.class.getDeclaredField("theUnsafe")с .setAccessible(true)и затем .get(null)получите его тоже
Amara

@ sparkleshy- Я удивлен, что это работает - менеджер по безопасности должен пометить это.
templatetypedef

5

Используйте его для эффективного доступа к большим объемам памяти, например, в вашем собственном воксельном движке! (т.е. игра в стиле Minecraft.)

По моему опыту, JVM часто не может устранить проверку границ там, где она действительно вам нужна. Например, если вы выполняете итерации по большому массиву, но фактический доступ к памяти скрывается за вызовом невиртуального * метода в цикле, JVM может выполнять проверку границ при каждом доступе к массиву, а не один раз перед петля. Таким образом, для потенциально большого прироста производительности вы можете исключить проверку границ JVM внутри цикла с помощью метода, использующего sun.misc.Unsafe для прямого доступа к памяти, обязательно проверяя границы в нужных местах. (Вы находитесь Gonna границы проверить на каком - то уровне, не так ли?)
* не виртуальным, я имею в виду, что JVM не должна динамически разрешать какой бы то ни было ваш конкретный метод, потому что вы правильно гарантировали, что класс / метод / экземпляр - это некоторая комбинация static / final / what-have-you.

Для моего собственного воксельного движка это привело к значительному увеличению производительности во время генерации фрагментов и сериализации (iow места, где я читал / записывал весь массив одновременно). Результаты могут отличаться, но если ваша проблема связана с отсутствием устранения границ, это решит проблему.

В этом есть некоторые потенциально серьезные проблемы: в частности, когда вы предоставляете возможность доступа к памяти без проверки границ клиентам вашего интерфейса, они, вероятно, будут злоупотреблять ею. (Не забывайте, что хакеры также могут быть клиентами вашего интерфейса ... особенно в случае воксельного движка, написанного на Java.) Таким образом, вы должны либо спроектировать свой интерфейс таким образом, чтобы доступ к памяти не мог быть нарушен, либо Вы должны быть чрезвычайно осторожны при проверке пользовательских данных, прежде чем они когда- либо смогут смешаться с вашим опасным интерфейсом. Учитывая катастрофические вещи, которые хакер может сделать с неконтролируемым доступом к памяти, вероятно, лучше всего использовать оба подхода.


4

Коллекции вне кучи могут быть полезны для выделения огромных объемов памяти и освобождения ее сразу после использования без вмешательства ГХ. Я написал библиотеку для работы с массивами / списками вне кучи на основе sun.misc.Unsafe.


4

Мы реализовали огромные коллекции, такие как Arrays, HashMaps, TreeMaps, используя Unsafe.
И чтобы избежать / минимизировать фрагментацию, мы реализовали распределитель памяти, используя понятия dlmalloc вместо unsafe.
Это помогло нам получить производительность в параллельном режиме.


3

Unsafe.park()и Unsafe.unpark()для создания пользовательских структур управления параллелизмом и механизмов совместного планирования.


24
публично доступно какjava.util.concurrent.locks.LockSupport
bestsss

1

Я не использовал его сам, но я полагаю, что если у вас есть переменная, которая только изредка читается более чем одним потоком (так что вы не хотите, чтобы она была энергозависимой), вы можете использовать ее putObjectVolatileпри записи в основной поток и readObjectVolatileпри выполнении редких чтений из других потоков.


1
но в соответствии с обсуждением на следующей нити, неудовлетворенные летучая почти так же быстро , как нелетучие вещества в любом случае stackoverflow.com/questions/5573782/...
pdeva

Вы не можете заменить изменчивую семантику обычными записями и изменчивыми чтениями ... это рецепт катастрофы, поскольку он может работать в одних настройках, но не в других. Если вы хотите иметь изменчивую семантику с одним потоком записи, вы можете использовать AtomicReference.lazySet для потока записи и get () для читателей (см. Этот пост для обсуждения этой темы). Изменчивые чтения относительно дешевы, но не бесплатны, смотрите здесь .
Ницан Вакарт

«... вы можете использовать putObjectVolatile при написании этого ...» Я не предлагал простые записи.
Мэтт Кринкло-Фогт

1

Вам это нужно, если вам нужно заменить функциональность, предоставляемую одним из классов, который использует его в настоящее время.

Это может быть пользовательская / более быстрая / более компактная сериализация / десериализация, более быстрая / более крупная буферная версия / версия с изменяемыми размерами ByteBuffer или добавление атомарной переменной, например, не поддерживаемой в настоящее время.

Я использовал это для всего этого в какое-то время.



0

Похоже, что объект доступен для работы на более низком уровне, чем обычно позволяет код Java. Если вы кодируете приложение высокого уровня, то JVM абстрагирует обработку памяти и другие операции от уровня кода, что облегчает программирование. Используя библиотеку Unsafe, вы эффективно выполняете низкоуровневые операции, которые обычно выполняются за вас.

Как сказал woliveirajr, «random ()» использует Unsafe для заполнения так же, как многие другие операции будут использовать функцию allocateMemory (), включенную в Unsafe.

Как программисту вы, вероятно, могли бы сойти с рук, никогда не нуждаясь в этой библиотеке, но иметь строгий контроль над низкоуровневыми элементами действительно полезно (вот почему все еще существует ассемблер и (в меньшей степени) код на C, распространяющийся в основных продуктах)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.