Статьи

Как и почему Unsafe используется в Java?

обзор

sun.misc.Unsafe был в Java, по крайней мере, еще с Java 1.4 (2004). В Java 9 Unsafe будет скрыт вместе со многими другими классами для внутреннего использования. улучшить ремонтопригодность JVM. Хотя до сих пор неясно, что именно заменит Unsafe, и я подозреваю, что это будет более чем одна вещь, которая заменит его, возникает вопрос, почему он вообще используется?

Делать то, что язык Java не позволяет, но все еще полезно.

Java не допускает многих приемов, доступных для языков более низкого уровня. Для большинства разработчиков это очень хорошая вещь, и она не только спасает вас от себя, но также спасает вас от ваших коллег. Это также упрощает импорт кода с открытым исходным кодом, потому что вы знаете, что существуют ограничения на возможный ущерб. Или, по крайней мере, есть пределы тому, сколько вы можете сделать случайно. Если вы попробуете достаточно сильно, вы все равно можете нанести урон.

Но почему бы вам даже не попробовать? При сборке библиотек многие (но не все) методы в Unsafe полезны, и в некоторых случаях нет другого способа сделать то же самое без использования JNI, что еще более опасно, и вы потеряете «один раз скомпилировать, запустить где угодно» »

Десериализация объектов

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

01
02
03
04
05
06
07
08
09
10
11
public class A implements Serializable {
    private final int num;
    public A(int num) {
        System.out.println("Hello Mum");
        this.num = num;
    }
 
    public int getNum() {
        return num;
    }
}

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

1
2
3
Unsafe unsafe = getUnsafe();
Class aClass = A.class;
A a = (A) unsafe.allocateInstance(aClass);

Вызов allocateInstance избавляет от необходимости вызывать соответствующий конструктор, когда он нам не нужен.

Потокобезопасный доступ к прямой памяти

Другое использование для Unsafe — это потокобезопасный доступ к памяти без кучи. ByteBuffer дает вам безопасный доступ к автономной памяти или прямой памяти, однако он не имеет никаких потоковобезопасных операций. Это особенно полезно, если вы хотите обмениваться данными между процессами.

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
import sun.misc.Unsafe;
import sun.nio.ch.DirectBuffer;
 
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
 
public class PingPongMapMain {
    public static void main(String... args) throws IOException {
        boolean odd;
        switch (args.length < 1 ? "usage" : args[0].toLowerCase()) {
            case "odd":
                odd = true;
                break;
            case "even":
                odd = false;
                break;
            default:
                System.err.println("Usage: java PingPongMain [odd|even]");
                return;        }
        int runs = 10000000;
        long start = 0;
        System.out.println("Waiting for the other odd/even");
        File counters = new File(System.getProperty("java.io.tmpdir"), "counters.deleteme");        counters.deleteOnExit();
 
        try (FileChannel fc = new RandomAccessFile(counters, "rw").getChannel()) {
            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
            long address = ((DirectBuffer) mbb).address();
            for (int i = -1; i < runs; i++) {
                for (; ; ) {
                    long value = UNSAFE.getLongVolatile(null, address);
                    boolean isOdd = (value & 1) != 0;
                    if (isOdd != odd)
                        // wait for the other side.
                        continue;
                    // make the change atomic, just in case there is more than one odd/even process
                    if (UNSAFE.compareAndSwapLong(null, address, value, value + 1))
                        break;
                }
                if (i == 0) {
                    System.out.println("Started");
                    start = System.nanoTime();
                }
            }
        }
        System.out.printf("... Finished, average ping/pong took %,d ns%n",
                (System.nanoTime() - start) / runs);
    }
 
    static final Unsafe UNSAFE;
 
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

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

В каждой программе он отображает одинаковые части кэша дисков в процессе. На самом деле в памяти есть только одна копия файла. Это означает, что память может быть распределена при условии, что вы используете поточно-ориентированные операции, такие как операции volatile и CAS.

Выход на i7-3970X это

В ожидании другого нечетного / четного
Началось
… Закончено, средний пинг / понг занял 83 нс

Это 83 нс время прохождения туда и обратно между двумя процессами. Если учесть, что для System V IPC требуется около 2500 нс, а IPC энергозависим, а не сохраняется, это довольно быстро.

Подходит ли Unsafe для работы?

Я бы не рекомендовал вам использовать Unsafe напрямую. Это требует гораздо большего тестирования, чем естественная разработка Java. По этой причине я предлагаю вам использовать библиотеку, где ее использование уже проверено. Если вы хотите использовать Unsafe самостоятельно, я советую вам проверить его использование в отдельной библиотеке. Это ограничивает использование Unsafe в вашем приложении и делает его более безопасным и небезопасным.

Вывод

Интересно, что Unsafe существует в Java, и вы можете поиграть с ним дома. У него есть некоторые рабочие приложения, особенно при написании низкоуровневых библиотек, но в целом лучше использовать библиотеку, которая использует Unsafe, которая была протестирована, чем использовать ее непосредственно самостоятельно.

Ссылка: Как и почему Unsafe используется в Java? от нашего партнера JCG Питера Лори в блоге Java Advent Calendar .