Чому існує sun.misc.Unsafe та як його використовувати в реальному світі? [зачинено]


267

Днями я натрапив на пакунок sun.misc.Unsafe і був вражений тим, що це може зробити.

Звичайно, клас незадокументований, але мені було цікаво, чи коли-небудь є вагома причина використовувати його. Які сценарії можуть виникнути там, де вам потрібно буде його використовувати? Як це можна використовувати в реальному сценарії?

Крім того, якщо ви зробите це знадобиться, чи це не означає, що з вашим дизайном щось, мабуть, не так?

Чому Java навіть включає цей клас?


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

2
Ця публікація обговорюється на мета: meta.stackoverflow.com/questions/299139/…
Джон Клементс

Відповіді:


159

приклади

  1. ВМ "інтрингіфікація". тобто CAS (Порівняти-заміняти), використовуваний у столах хеш-блоків, наприклад: sun.misc.Unsafe.compareAndSwapЯкщо він може робити справжні дзвінки JNI у рідний код, який містить спеціальні інструкції для CAS

    докладніше про CAS читайте тут http://en.wikipedia.org/wiki/Compare-and-swap

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

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

  4. Скомпілюйте оптимізацію часу для JVM. Висока продуктивність VM з використанням "магії", що вимагає операцій низького рівня. наприклад: 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, так чи не є ця операція досить марною?
pdeva

отримати адресу для тих, кого ви виділили
zudokod

що саме ти маєш на увазі під тим, що я виділив. це, мабуть, використовується в місцях, де об’єкти створені за допомогою «нового» оператора, тому моє питання.
pdeva

1
unsafe.allocateПоміть і поставте значення
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 для статичних полів. Спостереження полягає в тому, що з точки зору коду відображення існує лише дев'ять типів полів: вісім примітивних типів і Об'єкт. Використання класу Unsafe замість генерованих байт-кодів економить пам'ять та час завантаження для динамічно генерованих FieldAccessors. * /

  • SpikeSource

/ * Заключні поля, що надсилаються через провід .. як розімкнути та відтворити об'єкт на стороні, що приймає? Ми не хочемо викликати конструктор, оскільки він встановив би значення для кінцевих полів. Ми повинні відтворити остаточне поле точно так, як воно було на стороні відправника. Sun.misc.Unsafe робить це для нас. * /

Є багато інших прикладів, просто перейдіть за вищенаведеним посиланням ...


25

Цікаво, що я ніколи навіть не чув про цей клас (що, мабуть, гарна річ, насправді).

Одне, що спадає на думку, - це використовувати Unsafe # setMemory для знецінення буферів, які містили конфіденційну інформацію в один момент (паролі, ключі, ...). Ви можете навіть зробити це для полів "незмінних" об'єктів (тоді я знову думаю, що звичайне старе відображення могло б зробити і трюк і тут). Я не експерт з питань безпеки, але прийміть це із зерном солі.


4
I'd never even heard of this class... Я говорив вам про це стільки разів! зітхнути + :(
Тім Бендер

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

39
Ніколи про нього теж не чула, але я люблю їх park()документацію: "Блокуйте поточну нитку, повертаючись, коли відбувається балансування скасування, або балансувальний скасування відсутній, або потік перервано, або, якщо не абсолютний і час не дорівнює нулю, задані часові наносекунди минули або, якщо вони абсолютні, заданий термін у мілісекундах з моменту проходження Епохи, або помилково (тобто повернення без жодної причини) ". Майже настільки ж добре, як "пам'ять звільняється, коли програма виходить, або, випадковим чином, залежно від того, що відбувається раніше".
aroth

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

22

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

Операції CAS піддаються класам Atomic *. Функції маніпуляції з пам'яттю піддаються дії інструкцій DirectByteBuffer Sync (паркування, відпаркування) піддаються експозиції через AbstractQueuedSynchronizer, який, у свою чергу, використовується реалізацією блокування.


AtomicXXXUpdaters занадто повільні, і коли вони вам справді потрібні: CAS - ви не можете дозволити собі фактично їх використовувати. Якщо ви збираєтеся робити метал, ви не будете використовувати рівні абстракції та численні перевірки. Помилка CAS - це погано в циклі esp. коли апаратне рішення вирішує неправильно передбачити галузь (через велику суперечку), але мати ще кілька порівнянь / гілок просто боляче. Парк / паркінг піддаються впливу LockSupportне AQS (що останній є більше
імпульсом

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. Тим не менш, це досить дивовижна ідея, оскільки це тривіально впроваджувати генерацію байт-кодів у будь-якому разі, якщо зараз перевіряльник не перевіряє, чи вони метод оголошує закидні ... але це може бути назад несумісним, оскільки метадані про викинутий виняток були вільні відкинути.
bestsss

10

Клас небезпечний

Сукупність методів для виконання небезпечних операцій низького рівня. Хоча клас і всі методи є загальнодоступними, використання цього класу обмежене, оскільки лише довірений код може отримати його екземпляри.

Одне його використання в java.util.concurrent.atomicкласах:


6

Для ефективної копіювання пам'яті (швидше копіювати, ніж System.arraycopy () для коротких блоків принаймні); як використовуються кодеки Java LZF та Snappy . Вони використовують 'getLong' і 'putLong', які швидше, ніж робити копії бай-байтом; особливо ефективно при копіюванні таких елементів, як блоки 16/32/64 байт.


1
До, arraycopy використовує петлі SSE на x86-64, які кращі, ніж getLong/putLong(і ви також повинні обчислити адресу)
bestsss

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

так, використовуючи небезпечний, я не міг будь-якого змістовного виконання з-за спущеного імпульсу. Для декількох байт довгих копій на великих масивах get / putLong може працювати справді, коли компілятор повинен перевіряти довжини. Якийсь імпл. додайте огорожу пам'яті минулого System.arrayCopy (хоча її можна відключити / включити), щоб це могло бути справжнім винуватцем.
bestsss

Гаразд. Цілком можливо, що нові JDK змінили це; спочатку, коли я спостерігав більш швидку роботу (з JDK 1.6), я теж був здивований. Або, можливо, я забуваю якусь конкретну різницю у використанні. Це складні (і, можливо, нестабільні) оптимізації, навіть коли вони працюють, і важливо виміряти ефекти.
StaxMan

5

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

Коротше кажучи, він в основному існує, щоб дозволити виконавцям бібліотеки отримати доступ до базової машини без необхідності декларувати кожен метод у певних класах, таких як AtomicIntegerрідний. Вам не потрібно використовувати або турбуватися про це в рутинному програмуванні 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 для доступу безпосередньо до пам'яті, переконуючись, що будь-які межі перевіряють себе в потрібних місцях. (Ти є перевірити межі на якомусь рівні, правда?)
* під невіртуальним, я маю на увазі, що JVM не повинен динамічно вирішувати будь-який конкретний метод, тому що ви правильно гарантували, що клас / метод / екземпляр є деякою комбінацією статичного / кінцевого / того, що у вас є.

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

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


4

Колекції, що знаходяться поза купою, можуть бути корисними для розподілу величезної кількості пам’яті та для її розміщення відразу після використання без втручання GC. Я написав бібліотеку для роботи з невисокими масивами / списками на основі sun.misc.Unsafe.


4

Ми реалізували величезні колекції на зразок масивів, HashMaps, TreeMaps за допомогою Unsafe.
А щоб уникнути / мінімізувати фрагментацію, ми реалізували розподільник пам'яті, використовуючи поняття dlmalloc над небезпечним.
Це допомогло нам досягти ефективності одночасно.


3

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


24
загальнодоступний якjava.util.concurrent.locks.LockSupport
bestsss

1

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


1
але в відповідно до обговоренням наступного нитки, незадоволені летюча майже так само швидко , як нелеткі речовини в будь-якому випадку stackoverflow.com/questions/5573782 / ...
pdeva

ви не можете замінити летючу семантику простими записами, а мінливі читання ... це рецепт катастрофи, оскільки він може працювати в одних налаштуваннях, але не в інших. Якщо ви хочете мати мінливу семантику з однією записуючою ниткою, ви можете скористатися AtomicReference.lazySet на письмовій нитці та отримати () читачів (див. Цей пост для обговорення теми). Летючі читання відносно дешеві, але не безкоштовні, дивіться тут .
Нітсан Вакарт

"... ви можете використовувати putObjectVolatile при його написанні ..." Я не пропонував звичайні записи.
Метт Крінклав-Фогт

1

Він вам потрібен, якщо вам потрібно замінити функціонал, який надає один із класів, який використовує його зараз.

Це може бути спеціальна / швидша / більш компактна серіалізація / дезаріалізація, швидша / більша буферна / змінна зміна версії ByteBuffer, або додавання атомної змінної, наприклад, такої, яка не підтримується на даний момент.

Я використовував це для всіх цих у свій час.



0

Здається, об'єкт є доступністю для роботи на нижчому рівні, ніж те, що зазвичай дозволяє код Java. Якщо ви кодуєте додаток високого рівня, то JVM відключає обробку пам'яті та інші операції далеко від рівня коду, щоб її було легше програмувати. Використовуючи бібліотеку Unsafe, ви ефективно завершуєте операції низького рівня, які зазвичай виконуються для вас.

Як заявив woliveirajr, "випадковий ()" використовує Unsafe для виведення насіння так само, як і багато інших операцій використовуватиме функцію allocateMemory (), включену в Unsafe.

Як програміст, ви, ймовірно, можете піти з того, що ніколи не потребуєте цієї бібліотеки, але жорсткий контроль над елементами низького рівня дійсно корисний (саме тому досі існують складання та (в меншій мірі) C-коду, що переміщаються в основних продуктах)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.