Unsafe java что это
Перейти к содержимому

Unsafe java что это

  • автор:

Unsafe продолжает жить в Java 9

С началом работы над Java 9 было анонсировано удаление критически важных классов из пакетов sun.* (понятное дело Sun, а в последствии и Oracle заявляли, что их использование является собственным риском компаний и проектов), что вызвало шквал критики и недовольства со стороны сообщества (ибо highload решения для которых производительность это все, используют скрытые возможности sun.*). Предыстория началась 15 лет назад с выходом версии языка 1.4, за это время большое количество библиотек, фреймворков, приложений успели внедрить закрытый код в свой.

socmetr.unsafe.image

Вот только не полный перечень проектов, которые у всех на слуху: Scala, Kafka, Akka, Hadoop, Cassandra, Hazlecast и прочие…

Вроде бы новости без году неделя, однако постоянно приходится сталкиваться c тем, что люди не в курсе, и ожидают диких проблем с новым API в java 9 (может быть конечно и не без этого, однако. )

Реальность

Как ни странно, разработчики как из open jdk, так и из Oracle пошли на встречу сообществу и был принят документ JEP 260, если в двух словах: то решено оставить некоторые критически важные и широко используемые внутренние API, для которых пока не существует замены, однако это не означает, что будет гарантироваться их совместимость.

Таким образом решено оставить следующее API:

обеспечивает поддержку работы с сигналами (фактически IRC), может сообщить асинхронное событие вне JVM.

используется для работы непосредственно с памятью (обещают, что частично можно будет воспользоваться API из JEP 193: Variable Handles, также поговаривают что часть операций будет более эффективными).

говорит нам, какие классы находятся в стеке вызов (с выходом java 9, частично возможен переход на JEP 259: Stack-Walking API)

фактически master фабрика для создания объектов reflection

Не критическое API, которому уже существует замена, будет помечен как @Deprecated, уже начиная с Java 9, а начиная с Java 10 выводится из языка.

Уже сейчас доступна JDK9 Early Access на 24.02.2017 последний билд b158 (jdk9.java.net/download, jdk9.java.net/jigsaw) и если заглянуть внутрь, то можно обнаружить все выше перечисленные классы в модуле jdk.unsupported, также вместе с ними найдены:

com.sun.nio.file.ExtendedCopyOption
com.sun.nio.file.ExtendedOpenOption
com.sun.nio.file.ExtendedWatchEventModifier
com.sun.nio.file.SensitivityWatchEventModifier

обеспечивающие дополнительные возможности при работе с i/o-операциями.

Итого

Производители прислушались к сообществу, и в результате переход с пакетов sun.* обещает быть плавным и не критичным, а в случае проблем, можно спокойно спать и с 8 версией, буду рад ответить на вопросы (b.lutovich@socmetr.ru).

Небезопасный android часть 1: эксперименты с sun.misc.Unsafe

Java очень глубоко интегрирована в android и имеет в данной ОС свою нестандартную виртуальную машину — DVM/ART, поэтому многие детали реализации отличаются от привычных. А что насчёт внутреннего API sun.misc.Unsafe? В этом цикле статей с его помощью мы попытаемся максимально сломать виртуальную машину андроида.

Содержание

Часть 1. Введение. Создание arrayCast и его применение.

Часть 2. Классы-двойники. Получение списка всех полей, методов и конструкторов класса. Конвертация конструкторов в методы. Статический конструктор.

Введение

Начать стоит с того, что данный класс из себя представляет и для чего обычно используется. sun.misc.Unsafe существует с очень ранних версий java и необходим для выполнения действий, которые не предусмотрены языком, а их реализация в нативном коде по каким-то причинам нежелательна. Какие же возможности он предоставляет?

  • Object allocateInstance(Class cls) — создание объекта, без вызова конструктора. Все поля заполняются стандартными значениями (0, 0.0, null и т.п.)
  • XXX getXXX(Object obj, long offset) — чтение памяти, используя объект как указатель. Если он равен null, то смещение выступает в качестве нативного адреса
  • void setXXX(Object obj, long offset, XXX value) — запись в память по тем же правилам, что и при чтении
  • int arrayBaseOffset(Class cls), int arrayIndexScale(Class cls) — определение параметров массива (смещение от начала массива до первого элемента и размер элементов соответственно)
  • long allocateMemory(long bytes), void freeMemory(long address) — обёртки над malloc и free из Си — выделение и освобождение памяти вне кучи
  • void throwException(Throwable th) — «скрытное» бросание исключений (без их объявления в throws). Увы, но данный метод отсутствует в Unsafe андроида, поэтому придётся создавать его самостоятельно
  • А так же куча других полезных вещей, особенно для многопоточного программирования, которые в данный момент нам не нужны

Подготовка

Как получить доступ ко всему этому инструментарию? Класс Unsafe содержит публичный метод getUnsafe, только вот незадача — в нём есть проверка безопасности:

public final class Unsafe < private static final Unsafe theUnsafe = new Unsafe(); @CallerSensitive public static Unsafe getUnsafe() < Classcaller = Reflection.getCallerClass(); // Этот код не даёт получить экземпляр ползовательским классам if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; > >

Один из способов обойти её — прямой доступ к статическому полю theUnsafe через рефлексию

public static Unsafe getUnsafe() < try < // получаем поле Field field = Unsafe.class.getDeclaredField("theUnsafe"); // делаем его доступным для использования field.setAccessible(true); // получаем экземпляр sun.misc.Unsafe return (Unsafe) field.get(null); >catch (Exception e) < throw new RuntimeException(e); >>

Этот код настолько часто используется в разнообразных библиотеках, что его можно назвать классическим. Он даже не вызывает предупреждений во время выполнения (а вот доступ к конструктору вместо поля — ещё как).

Теперь нужно восполнить отсутствие метода throwException с помощью сломанного класса Thrower — мы создадим в нём метод с нужной сигнатурой, и насильно удалим throws Throwable:

Вариант кода на Smali — ассемблере Dalvik:

.class public Lcom/v7878/Thrower; .super Ljava/lang/Object; .method public constructor ()V .registers 1 invoke-direct < p0 >, Ljava/lang/Object;->()V return-void .end method .method public static throwException(Ljava/lang/Throwable;)V .registers 1 throw p0 .end method

То же самое, но на java:

// Не скомпилируется,так как нет throws Throwable public class Thrower < public static void throwException(Throwable th) < throw th; >>

Эксперименты

Время перейти к самой интересной части — экспериментам.

arrayCast

Что произойдёт если мы попробуем обратиться к объекту класса A как к объекту класса B, например попытаемся вызвать метод или получить поле, которого у него нет? Язык защищает от таких поступков ещё на этапе компиляции, и не даёт нам творить беспредел, но ведь теперь мы можем это игнорировать, да? Обмануть компилятор (а заодно и верификатор байт-кода) можно положив объект в поле неправильного типа и обращаясь уже к нему:

// Смещение от начала массива до первого элемента // int ARRAY_OBJECT_BASE_OFFSET = arrayBaseOffset(Object[].class); class A < public int a; >class B < public int b; >A obj = new A(); obj.a = 100; B[] array = new B[1]; // array[0] = obj; putObject(array, ARRAY_OBJECT_BASE_OFFSET, obj); System.out.println(array[0].b); // System.out: 100

В данном примере мы положили объект типа A (со значением 100 в поле a) как первый элемент массива B и получили значение 100 из поля b. Как это произошло?

Объект java представляет собой ссылку на память в куче, где лежат его данные — поля. Каждое поле имеет своё смещение от начала объекта (они отсортированы по уменьшению размера и по алфавиту. Сначала идут поля суперкласса). Например класс java.lang.Object имеет всего 2 поля:

public class Object < private transient Class shadow$_klass_; private transient int shadow$_monitor_; >

shadow$_klass_ содержит тип объекта, а shadow$_monitor_ данные монитора (используется для методов wait* и notify* и блоков synchronized) и/или кешированый хешкод. Первое поле будет иметь смещение 0 и размер 4 (размер поля не примитивного типа на андроиде равен 4 (даже на 64-битных устройствах!)), второе поле — смещение 4 и такой же размер.

Переходя к изначальному примеру — поле a типа A имеет то же самое смещение, что и поле b типа B, поэтому доступ ко второму даёт первое. Обратите внимание, что ни один объект не сменил реальный тип.

Теперь можно обобщить полученный опыт и сделать отдельный метод, выполняющий функцию оператора reinterpret_cast из C++

// Смещение от начала массива до первого элемента // int ARRAY_OBJECT_BASE_OFFSET = arrayBaseOffset(Object[].class); // размер элемента массива объектов (на андроиде всегда должен быть 4) // int ARRAY_OBJECT_INDEX_SCALE = arrayIndexScale(Object[].class); public T[] arrayCast(Class clazz, Object. data) < // нам нужен массив объектов, а не чего-то ещё if(clazz.isPrimitive()) < throw new IllegalArgumentException(); >// создаём массив типа T T[] out = (T[]) Array.newInstance(clazz, data.length); // переносим все объекты в массив for (int i = 0; i < data.length; i++) < putObject(out, ARRAY_OBJECT_BASE_OFFSET + i * ARRAY_OBJECT_INDEX_SCALE, data[i]); >return out; >

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

Теперь давайте применим arrayCast для изменения private final поля, чего нельзя сделать с помощью обычной рефлексии:

class A < private final int value; public A(int value) < this.value = value; >@Override public String toString() < return Integer.toString(value); >> class B < public int value; >A obj = new A(100); // приводим obj к типу B B[] array = arrayCast(B.class, obj); // меняем значение array[0].value = -1; // выводим в консоль System.out.println(obj); // System.out: -1

Получается, что при известном строении объекта с его полями можно творить что угодно, но как насчёт методов?

class A < private final int value; public A(int value) < this.value = value; >@Override public String toString() < return Integer.toString(value); >> class B < public int value; public void set(int x) < value = x; >> A obj = new A(100); // приводим obj к типу B B[] array = arrayCast(B.class, obj); // меняем значение array[0].set(-1); // выводим в консоль System.out.println(obj); // ожидаем получить: System.out: -1 // получаем: zygote A [runtime.cc:492] Runtime aborting. // и огромный столб текста с аварийным дампом

Что произошло? Почему такие ужасные последствия у, казалось бы, маленьких изменений? Причина кроется в способе вызова методов — по индексу в списке внутри класса объекта. Мы вызываем метод B.set(int), допустим он имеет индекс 1, но вызываем то мы его на объекте типа A! У него другой список, и под индексом 1, может быть, идёт конструктор. Это несоответствие и вызывает ошибку.

Но почему такого не происходит при доступе к полям? Ответ прост — они всегда имеют постоянное смещение и его нет нужды вычислять каждый раз по индексу поля в классе, а значит, для оптимизации всегда идёт прямой доступ по заранее просчитанному смещению.

В будущем мы периодически будем обращаться к arrayCast для достижения своих целей. На этом вводная часть подходит к концу.

Весь исходный код можно найти здесь.

  • java
  • android
  • unsafe
  • reinterpret_cast
  • ненормальное программирование

Unsafe java что это

Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нём некорректно.
Необходимо обновить браузер или попробовать использовать другой.

Unsafe Java I — Небезопасная жаба

Дата публикации 20 май 2006

Unsafe Java I — Небезопасная жаба — Архив WASM.RU

  1. Класс sun.misc.Unsafe
  2. Структуры виртуальной машины
  3. Особенности версии 1.5
  4. Применение на практике
  5. Бесконечный final

public static Unsafe getUnsafe()
Class class1 = Reflection.getCallerClass(2);
if(class1.getClassLoader() != null)
throw new SecurityException(«Unsafe»);
return theUnsafe;

К счастью существует еще внутренняя переменная theUnsafe, до которой мы можем добраться с помощью Reflection. Всю черновую работу соберем в один класс (назовем его UnsafeUtil), который будем расширять по мере надобности.

public class UnsafeUtil < public static Unsafe unsafe; private static long fieldOffset; private static UnsafeUtil instance = new UnsafeUtil(); private Object obj; Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe)f.get(null); fieldOffset = unsafe.objectFieldOffset(UnsafeUtil.class.getDeclaredField("obj")); > catch (Exception ex) < throw new RuntimeException(e);

Конечно можно просто внести UnsafeUtil в список загружаемых Bootloader’ом классов (указав путь в ключе -Xbootclasspath/a) и вызывать getUnsafe() в соответствии с замыслом Sun. Беда в том, что тогда все использующие UnsafeUtil классы также должны быть прописаны в bootclasspath’е (см. главу «5.3 Creation and Loading» в VM spec). Правда например package java.nio как-то ухитряется обходить это ограничение, но как именно пока не очень понятно. К тому же этот способ выходит за рамки «чистого» кода, так как требует дополнительных стартовых опций для виртуальной машины. Так что не будем мудрствовать и ограничимся чтением theUnsafe.

В первую очередь нам понадобятся естественно операции референцирования и дереференцирования, ObjectToAddress и AddressToObject соответственно.

public static long ObjectToAddress (Object o)< instance.obj = o; return unsafe.getLong(instance, fieldOffset); public static Object AddressToObject (long address)< unsafe.putLong(instance, fieldOffset, address); return instance.obj;

С ними мы уже достаточно хорошо вооружены в техническом плане, не хватает только информации по внутреннему устройству Явы. Ее мы найдем в следующем разделе.

Очень похожую реализацию кстати сделал Don Schwarz (http://don.schwarz.name/index.php?p=30). Это одно из очень немногих мест, где можно найти хоть какие-то примеры работы с классом Unsafe. К сожалению Don в свое время не оценил потенциал низкоуровнего программирования в Яве и остановился, сделав всего пару робких шагов. Мы же пойдем дальше.

2. Структуры виртуальной машины

Теперь посмотрим, в каком виде виртуальная машина (версии 1.4) хранит данные в памяти. Поскольку Ява работает с классами и их инстанциями, то ими и займемся.

Инстанция:
instance_struct < 0 magic // всегда равен 1 4 class // указатель на структуру класса, class_struct* 8 . // Дальше идут подряд переменные инстанции(то есть все которые не static), 12 . // порядок пока не очень понятен, судя по всему в порядке объявления и 16 . // обьекты перед примитивными типами

Переменные типа double и long занимают 64 бита, остальные по 32. В памяти инстанции выравниваются по 64-битной границе, дополняются при необходимости нулями. То есть по сути мы имеем обыкновенную сишную структуру плюс указатель на ее описание.

Класс:
class_struct < 0 magic // всегда равен 1

4 class // class_struct*, указатель на структуру класса более высокого уровня, зачем нужен — непонятно

8 . // значение неизвестно

12 super_count // количество уровней наследования: 0x18 — один(наследует от Object), 0х1c — два и т.д. до восьми(0х34), потом 0х10. У интерфейсов тоже 0х10.

16 interface // class_struct*, указатель на какой-либо из интерфейсов класса (на какой именно непонятно), часто просто 0

20 interface_list // указатель на массив с элементами типа class_struct*, все интерфейсы класса

36 . // указатели на структуры восьми высших суперклассов начиная от Object и кончая this (если поместится)

56 size // размер инстанции класса в DWORD’ах

60 this_class // instance_struct*, указатель на инстанцию java.lang.Class соответствующую данному классу

64 access_flags // доступ к классу как описано в VM spec ( 0х1 — public, 0х10 — final и т.д.)

Здесть я привел только те куски, которые мы будем использовать в дальнейшем и в которых я более или менее уверен. На самом деле class_struct значительно длиннее и содержит кроме того указатели на функции класса, статические переменные и все остальное, что может понадобится виртуальной машине. Все эти структуры по понятным причинам нигде не документированы и разбираться надо вручную — хоть и несложно, но достаточно трудоемко. Если у кого-то есть желание помочь, буду только рад.

3. Особенности версии 1.5

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

class_struct_1_5 < 0 magic // всегда равен 1

4 class // class_struct*, указатель на структуру класса более высокого уровня, зачем нужен — непонятно

8 . // значение неизвестно
12 size // размер инстанции класса в DWORD’ах

16 super_count // количество уровней наследования: 0x20 — один(наследует от Object), 0х24 — два и т.д. до восьми(0х38), потом 0х14. У интерфейсов тоже 0х14.

20 interface // class_struct*, указатель на какой-либо из интерфейсов класса (на какой именно непонятно), часто просто 0

24 interface_list // указатель на массив с элементами типа class_struct*, все интерфейсы класса

40 . // указатели на структуры восьми высших суперклассов начиная от Object и кончая this (если поместится)

60 this_class // instance_struct*, указатель на инстанцию java.lang.Class соответствующую данному классу

Путеводитель по sun.misc.Unsafe

В этой статье мы рассмотрим увлекательный класс, предоставляемый JRE — Unsafe из пакета sun.misc . Этот класс предоставляет нам низкоуровневые механизмы, предназначенные для использования только основной библиотекой Java, а не обычными пользователями.

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

2. Получение экземпляра небезопасного

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

Способ получения экземпляра — через статический метод getUnsafe(). Предостережение в том, что по умолчанию это вызовет исключение SecurityException.

К счастью, мы можем получить экземпляр с помощью отражения:

 Field f = Unsafe.class.getDeclaredField("theUnsafe");  f.setAccessible(true);  unsafe = (Unsafe) f.get(null); 

3. Создание экземпляра класса с помощью Unsafe

Допустим, у нас есть простой класс с конструктором, который устанавливает значение переменной при создании объекта:

 class InitializationOrdering    private long a;    public InitializationOrdering()    this.a = 1;   >    public long getA()    return this.a;   >   > 

Когда мы инициализируем этот объект с помощью конструктора, метод getA() вернет значение 1:

 InitializationOrdering o1 = new InitializationOrdering();   assertEquals(o1.getA(), 1); 

Но мы можем использовать метод allocateInstance () с помощью Unsafe. Он выделит память только для нашего класса и не вызовет конструктор:

 InitializationOrdering o3   = (InitializationOrdering) unsafe.allocateInstance(InitializationOrdering.class);    assertEquals(o3.getA(), 0); 

Обратите внимание, что конструктор не был вызван, поэтому метод getA() вернул значение по умолчанию для типа long — 0.

4. Изменение личных полей

Допустим, у нас есть класс, который содержит секретное частное значение:

 class SecretHolder    private int SECRET_VALUE = 0;    public boolean secretIsDisclosed()    return SECRET_VALUE == 1;   >   > 

Используя метод putInt() из Unsafe, мы можем изменить значение частного поля SECRET_VALUE , изменяя/искажая состояние этого экземпляра:

 SecretHolder secretHolder = new SecretHolder();    Field f = secretHolder.getClass().getDeclaredField("SECRET_VALUE");  unsafe.putInt(secretHolder, unsafe.objectFieldOffset(f), 1);    assertTrue(secretHolder.secretIsDisclosed()); 

Как только мы получим поле вызовом отражения, мы можем изменить его значение на любое другое значение int , используя метод Unsafe .

5. Создание исключения

Код, вызываемый через Unsafe , не проверяется компилятором так же, как обычный код Java. Мы можем использовать метод throwException() для создания любого исключения, не ограничивая вызывающую программу обработкой этого исключения, даже если это проверенное исключение:

 @Test(expected = IOException.class)   public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt()    unsafe.throwException(new IOException());   > 

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

6. Память вне кучи

Если приложению не хватает доступной памяти на JVM, мы можем в конечном итоге заставить процесс GC запускаться слишком часто. В идеале нам нужна особая область памяти, вне кучи и не контролируемая процессом GC.

Метод allocateMemory() из класса Unsafe дает нам возможность выделять огромные объекты из кучи, а это означает, что эта память не будет видна и принята во внимание сборщиком мусора и JVM .

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

Допустим, мы хотим создать большой массив байтов памяти вне кучи. Для этого мы можем использовать метод allocateMemory() :

 class OffHeapArray    private final static int BYTE = 1;   private long size;   private long address;    public OffHeapArray(long size) throws NoSuchFieldException, IllegalAccessException    this.size = size;   address = getUnsafe().allocateMemory(size * BYTE);   >    private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException    Field f = Unsafe.class.getDeclaredField("theUnsafe");   f.setAccessible(true);   return (Unsafe) f.get(null);   >    public void set(long i, byte value) throws NoSuchFieldException, IllegalAccessException    getUnsafe().putByte(address + i * BYTE, value);   >    public int get(long idx) throws NoSuchFieldException, IllegalAccessException    return getUnsafe().getByte(address + idx * BYTE);   >    public long size()    return size;   >    public void freeMemory() throws NoSuchFieldException, IllegalAccessException    getUnsafe().freeMemory(address);   > 

В конструкторе OffHeapArray мы инициализируем массив заданного размера. Мы сохраняем начальный адрес массива в поле адреса . Метод set() принимает индекс и заданное значение , которое будет храниться в массиве. Метод get() извлекает значение байта, используя его индекс, который является смещением от начального адреса массива.

Затем мы можем выделить этот массив вне кучи, используя его конструктор:

 long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;   OffHeapArray array = new OffHeapArray(SUPER_SIZE); 

Мы можем поместить N значений байтов в этот массив, а затем извлечь эти значения, суммируя их, чтобы проверить, правильно ли работает наша адресация:

 int sum = 0;   for (int i = 0; i  100; i++)    array.set((long) Integer.MAX_VALUE + i, (byte) 3);   sum += array.get((long) Integer.MAX_VALUE + i);   >    assertEquals(array.size(), SUPER_SIZE);   assertEquals(sum, 300); 

В конце нам нужно освободить память обратно в ОС, вызвав freeMemory().

7. Операция сравнения и замены

Очень эффективные конструкции из пакета java.concurrent , такие как AtomicInteger, используют методы compareAndSwap() из Unsafe , чтобы обеспечить максимально возможную производительность. Эта конструкция широко используется в алгоритмах блокировки без блокировки, которые могут использовать инструкции процессора CAS для обеспечения значительного ускорения по сравнению со стандартным механизмом пессимистической синхронизации в Java.

Мы можем создать счетчик на основе CAS, используя метод compareAndSwapLong() из Unsafe :

 class CASCounter    private Unsafe unsafe;   private volatile long counter = 0;   private long offset;    private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException    Field f = Unsafe.class.getDeclaredField("theUnsafe");   f.setAccessible(true);   return (Unsafe) f.get(null);   >    public CASCounter() throws Exception    unsafe = getUnsafe();   offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));   >    public void increment()    long before = counter;   while (!unsafe.compareAndSwapLong(this, offset, before, before + 1))    before = counter;   >   >    public long getCounter()    return counter;   >   > 

В конструкторе CASCounter мы получаем адрес поля счетчика, чтобы потом использовать его в методе increment() . Это поле должно быть объявлено как volatile, чтобы его могли видеть все потоки, записывающие и считывающие это значение. Мы используем метод objectFieldOffset() для получения адреса памяти поля смещения .

Наиболее важной частью этого класса является метод increment() . Мы используем compareAndSwapLong() в цикле while для увеличения ранее извлеченного значения, проверяя, изменилось ли это предыдущее значение с момента его извлечения.

Если да, то мы повторяем эту операцию до тех пор, пока не добьемся успеха. Здесь нет блокировки, поэтому этот алгоритм называется lock-free.

Мы можем протестировать наш код, увеличив общий счетчик из нескольких потоков:

 int NUM_OF_THREADS = 1_000;   int NUM_OF_INCREMENTS = 10_000;   ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);   CASCounter casCounter = new CASCounter();    IntStream.rangeClosed(0, NUM_OF_THREADS - 1)   .forEach(i -> service.submit(() -> IntStream   .rangeClosed(0, NUM_OF_INCREMENTS - 1)   .forEach(j -> casCounter.increment()))); 

Затем, чтобы подтвердить правильность состояния счетчика, мы можем получить от него значение счетчика:

 assertEquals(NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter()); 

8. Парковка/разпарковка

В Unsafe API есть два интересных метода, которые используются JVM для переключения потоков контекста. Когда поток ожидает какого-либо действия, JVM может заблокировать этот поток, используя метод park() из класса Unsafe .

Он очень похож на метод Object.wait() , но вызывает собственный код ОС, таким образом, используя некоторые особенности архитектуры для достижения наилучшей производительности.

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

9. Заключение

В этой статье мы рассмотрели класс Unsafe и его наиболее полезные конструкции.

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

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *