Java Многопоточность серии --sun.misc.Unsafe понимание

предисловие

Опасное класс под sun.misc пакет, а не часть стандарта Java. Но многие Java класса фонда библиотека, включая некоторые библиотеки разработки высокопроизводительных широко используется основана на развитии небезопасного класса, такие как Нетти, Hadoop, Кафка и так далее.

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

Опасное Java можно рассматривать как оставить в задней двери, он предлагает некоторые низкоуровневые операции, такие как прямой доступ к памяти, планирование потоков.

 Официальный не рекомендует использование небезопасного.

 Использование класса

 Получает небезопасные объекты

К сожалению, небезопасный объект не может быть непосредственно new Unsafe()или позвонив по телефону Unsafe.getUnsafe()получить следующие причины:

* Не напрямую new Unsafe(), потому что Unsafeпредназначен для одномодового варианта, конструктор является частным;

* Не по телефонуUnsafe.getUnsafe()获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载,从getUnsafe的源码中也可以看出来,如下:

    @CallerSensitive
    public static Unsafe getUnsafe() {
        //得到调用该方法的Class对象
        Class cc = Reflection.getCallerClass();
        //判断调用该方法的类是否是引导类加载器(bootstrap class loader)
        //如果不是的话,比如由AppClassLoader调用该方法,则抛出SecurityException异常
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        //返回单例对象
        return theUnsafe;
    }

В то время как мы не можем быть получено указанным выше способом объекты небезопасный, но имеют некоторые частные небезопасного класс имеет статический глобальные свойства theUnsafe(Unsafe实例对象), за счет отражения, объект может быть приобретен поле атрибута theUnsafe элемент , соответствующий, и расположены , чтобы быть доступным, в частности , для получения theUnsafe объект, следующий код: 

package concurrency;

import java.lang.reflect.Field;
import sun.misc.Unsafe;
import sun.reflect.Reflection;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException {
        // 通过反射得到theUnsafe对应的Field对象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 设置该Field为可访问
        field.setAccessible(true);
        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe);

    }
}

Анализ исходного кода небезопасного класса 

Большинство API является родным небезопасные методы для достижения путем вызова JNI кода (JNI: Java Native Interface для локального вызова JAVA), в том числе по следующим категориям:

1) Класс связаны между собой. Основной поставщик класса и методы работы его статические полей.

2) Объект связанных. Объект Основных методов работы и обеспечивают его поле.

3) Arrray связаны между собой. Метод работы и обеспечивает основные элементы массива.

4) Параллельное отношение. В основном обеспечивает примитивы синхронизации низкого уровня, например, CAS, планирование потоков, летучие, барьеры памяти.

5) Память связанных. Обеспечивает прямой метод доступа к памяти (прямая операция локального обхода кучи памяти Java), вы можете сделать то же самое, как C, как свободное использование ресурсов памяти системы.

6) системы, связанные с. Основная память возвращаются некоторой информация низкого уровня, такие как размер адреса, размер страницы памяти.

Класс для облегчения последующих примеров 

public class VO
{
	public int a = 0;
	
	public long b = 0;
	
	public static String c= "123";
	
	public static Object d= null;
	
	public static int e = 100;
}

1.Class связанные 

//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
  
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class<?> c);
//确保类被初始化
public native void ensureClassInitialized(Class<?> c);
//定义一个类,可用于动态创建类
public native Class<?> defineClass(String name, byte[] b, int off, int len,
     ClassLoader loader,
     ProtectionDomain protectionDomain);
//定义一个匿名类,可用于动态创建类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

Получает значение свойства статического поля 

VO.e = 1024;
Field sField = VO.class.getDeclaredField("e");
Object base = unsafe.staticFieldBase(sField);
long offset = unsafe.staticFieldOffset(sField);
System.out.println(unsafe.getInt(base, offset));//1024

 2.Object связанные

//获得对象的字段偏移量 
public native long objectFieldOffset(Field f); 
//获得给定对象地址偏移量的int值
public native int getInt(Object o, long offset);
//设置给定对象地址偏移量的int值
public native void putInt(Object o, long offset, int x);

//创建对象,但并不会调用其构造方法。如果类未被初始化,将初始化类。
public native Object allocateInstance(Class<?> cls)
 throws InstantiationException;

Чтение значения экземпляра объекта поля 

//获取实例字段的属性值
VO vo = new VO();
vo.a = 10000;
long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
int va = unsafe.getInt(vo, aoffset);
System.out.println("va="+va);

3. Массив связанных 

/**
 * Report the offset of the first element in the storage allocation of a
 * given array class. If {@link #arrayIndexScale} returns a non-zero value
 * for the same class, you may use that scale factor, together with this
 * base offset, to form new offsets to access elements of arrays of the
 * given class.
 *
 * @see #getInt(Object, long)
 * @see #putInt(Object, long, int)
 */
//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);
//boolean、byte、short、char、int、long、float、double,及对象类型均有以下方法
/** The value of {@code arrayBaseOffset(boolean[].class)} */
public static final int ARRAY_BOOLEAN_BASE_OFFSET
 = theUnsafe.arrayBaseOffset(boolean[].class);
  
/**
 * Report the scale factor for addressing elements in the storage
 * allocation of a given array class. However, arrays of "narrow" types
 * will generally not work properly with accessors like {@link
 * #getByte(Object, int)}, so the scale factor for such classes is reported
 * as zero.
 *
 * @see #arrayBaseOffset
 * @see #getInt(Object, long)
 * @see #putInt(Object, long, int)
 */
//返回数组中每一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);
  
//boolean、byte、short、char、int、long、float、double,及对象类型均有以下方法
/** The value of {@code arrayIndexScale(boolean[].class)} */
public static final int ARRAY_BOOLEAN_INDEX_SCALE
 = theUnsafe.arrayIndexScale(boolean[].class);

Получить размер головы и размер элемента массива 

// 数组第一个元素的偏移地址,即数组头占用的字节数
int[] intarr = new int[0];
System.out.println(unsafe.arrayBaseOffset(intarr.getClass()));
 
// 数组中每个元素占用的大小
System.out.println(unsafe.arrayIndexScale(intarr.getClass()));

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

4. Параллельное связанные 

4.1CAS связанные

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

//更新变量值为x,如果当前值为expected
//o:对象 offset:偏移量 expected:期望值 x:新值
public final native boolean compareAndSwapObject(Object o, long offset,
       Object expected,
       Object x);
  
public final native boolean compareAndSwapInt(Object o, long offset,
      int expected,
      int x);
  
public final native boolean compareAndSwapLong(Object o, long offset,
      long expected,
      long x);

 Начиная с Java 8, небезопасный предоставляет следующие методы:

//增加
public final int getAndAddInt(Object o, long offset, int delta) {
 int v;
 do {
 v = getIntVolatile(o, offset);
 } while (!compareAndSwapInt(o, offset, v, v + delta));
 return v;
}
  
public final long getAndAddLong(Object o, long offset, long delta) {
 long v;
 do {
 v = getLongVolatile(o, offset);
 } while (!compareAndSwapLong(o, offset, v, v + delta));
 return v;
}
//设置
public final int getAndSetInt(Object o, long offset, int newValue) {
 int v;
 do {
 v = getIntVolatile(o, offset);
 } while (!compareAndSwapInt(o, offset, v, newValue));
 return v;
}
  
public final long getAndSetLong(Object o, long offset, long newValue) {
 long v;
 do {
 v = getLongVolatile(o, offset);
 } while (!compareAndSwapLong(o, offset, v, newValue));
 return v;
}
  
public final Object getAndSetObject(Object o, long offset, Object newValue) {
 Object v;
 do {
 v = getObjectVolatile(o, offset);
 } while (!compareAndSwapObject(o, offset, v, newValue));
 return v;

Планирование 4.2 резьбы, связанные 

//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁
public native void monitorEnter(Object o);
//释放对象锁
public native void monitorExit(Object o);
//尝试获取对象锁,返回true或false表示是否获取成功
public native boolean tryMonitorEnter(Object o);

 4.3volatile, связанные с чтением и письменной форме

Основные типы (логическое, байт, символ, короткое, INT, длинные, с плавающей точкой, двойной) Java и эталонный объект в типе имеет следующие методы.

//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
//相当于getObject(Object, long)的volatile版本
public native Object getObjectVolatile(Object o, long offset);
  
//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
//相当于putObject(Object, long, Object)的volatile版本
public native void putObjectVolatile(Object o, long offset, Object x);



/**
 * Version of {@link #putObjectVolatile(Object, long, Object)}
 * that does not guarantee immediate visibility of the store to
 * other threads. This method is generally only useful if the
 * underlying field is a Java volatile (or if an array cell, one
 * that is otherwise only accessed using volatile accesses).
 */
public native void putOrderedObject(Object o, long offset, Object x);
  
/** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */
public native void putOrderedInt(Object o, long offset, int x);
  
/** Ordered/Lazy version of {@link #putLongVolatile(Object, long, long)} */
public native void putOrderedLong(Object o, long offset, long x);

4.4 память, связанные с барьерами

Java 8 вводится для определения барьеров памяти, чтобы избежать кода переназначения.

//内存屏障,禁止load操作重排序,即屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序,即屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();

5. прямого доступа к памяти (не динамической памяти)

allocateMemory выделенной памяти вручную свободный (не собирается GC)

//(boolean、byte、char、short、int、long、float、double)都有以下get、put两个方法。 
//获得给定地址上的int值
public native int getInt(long address);
//设置给定地址上的int值
public native void putInt(long address, int x);
//获得本地指针
public native long getAddress(long address);
//存储本地指针到给定的内存地址
public native void putAddress(long address, long x);
  
//分配内存
public native long allocateMemory(long bytes);
//重新分配内存
public native long reallocateMemory(long address, long bytes);
//初始化内存内容
public native void setMemory(Object o, long offset, long bytes, byte value);
//初始化内存内容
public void setMemory(long address, long bytes, byte value) {
 setMemory(null, address, bytes, value);
}
//内存内容拷贝
public native void copyMemory(Object srcBase, long srcOffset,
    Object destBase, long destOffset,
    long bytes);
//内存内容拷贝
public void copyMemory(long srcAddress, long destAddress, long bytes) {
 copyMemory(null, srcAddress, null, destAddress, bytes);
}
//释放内存
public native void freeMemory(long address);

 6. Системы связанной

//返回指针的大小。返回值为4或8。
public native int addressSize();
  
/** The value of {@code addressSize()} */
public static final int ADDRESS_SIZE = theUnsafe.addressSize();
  
//内存页的大小。
public native int pageSize();

расширение знаний

Замечу, что существует способ вышеперечисленных

  • stateOffset = unsafe.objectFieldOffset (поле) может быть понят из названия метода: смещение приобретенного свойства объекта Field объекта.

Чтобы понять это смещение, вы должны понимать модель памяти Java

Модель памяти Java

 

Java-объекты, хранящиеся в памяти макета можно разделить на три зоны: объект заголовка (заголовок), данные экземпляра (экземпляра данных) и выравнивание заполнения (Padding), простое понимание:

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

В качестве простого примера, следующие категории:

class VO {
    public int a = 0;
    public int b = 0;
}

 ВО VO = новый ВО (); когда, Java, открыт в адрес памяти, заголовок объекта содержит фиксированную длину (предполагается, что 16 байт, биты разные машин / сжатие заголовка объекта будет влиять на длину заголовка объекта) + примеры данных (A + 4 байта б 4 байта) + обивка.

Указанный прямой вывод здесь, сказали мы выше, смещение воплощено здесь, как свойство смещения выше 16, смещение составляет 20 свойства б.

В небезопасным внутри класса, мы нашли способ unsafe.getInt (объект, смещение);
от unsafe.getInt (VO, 16) может быть получено в значении vo.a. Не думаю , отражение? В самом деле, что отражает основной Java используется UNSAFE

Дальнейшее отражение

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

Посмотреть свойство смещения рекомендует инструмент как Жол: http://openjdk.java.net/projects/code-tools/jol/
с Жолом можно легко просмотреть расположение памяти Java, в сочетании с кодом объяснить

public class VO {
    public int a = 0;
    public long b = 0;
    public String c= "123";
    public Object d= null;
    public int e = 100;
    public static int f= 0;
    public static String g= "";
    public Object h= null;
    public boolean i;
}
public static void main(String[] args) throws Exception {
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseClass(VO.class).toPrintable());
        System.out.println("=================");
        Unsafe unsafe = getUnsafeInstance();
        VO vo = new VO();
        vo.a=2;
        vo.b=3;
        vo.d=new HashMap<>();
        long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
        System.out.println("aoffset="+aoffset);
        // 获取a的值
        int va = unsafe.getInt(vo, aoffset);
        System.out.println("va="+va);
    }

    public static Unsafe getUnsafeInstance() throws Exception {
        // 通过反射获取rt.jar下的Unsafe类
        Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeInstance.setAccessible(true);
        // return (Unsafe) theUnsafeInstance.get(null);是等价的
        return (Unsafe) theUnsafeInstance.get(Unsafe.class);
    }

В моей локальной машине результаты теста следующие: 

# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

com.ha.net.nsp.product.VO object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0    12                    (object header)                           N/A
     12     4                int VO.a                                      N/A
     16     8               long VO.b                                      N/A
     24     4                int VO.e                                      N/A
     28     1            boolean VO.i                                      N/A
     29     3                    (alignment/padding gap)                  
     32     4   java.lang.String VO.c                                      N/A
     36     4   java.lang.Object VO.d                                      N/A
     40     4   java.lang.Object VO.h                                      N/A
     44     4                    (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

=================
aoffset=12
va=2

 В результатах, мы обнаружили, что:

  • 1, мое локальное окружение виртуальной машины открыто и сжатое сжатие 64-бит, объекты выравнивание 8-байтовое
  • 2, заголовок В.О. памяти класс объектов макета содержит 12 байт, Int данных 4-байтовые данные, имеет размер 8 байтов, а другой строки объекта составляет 4 байта, 4 байта, наконец, выравненные.
  • 3, порядок не соответствует раскладке памяти VO класса с объявлением собственности.
  • 4, статические атрибуты макета класса памяти VO VO нет, потому что он принадлежит к классу класса.
  • 5, VO класс может быть определен по количеству байтов, занимаемых объектом, это пространство было определено во время компиляции (Примечание: Это пространство не является реальным объектом пространства).
  • 6, как описано выше, 12 могут быть считаны с помощью значения смещения 2 хранится здесь.

Выйдите из новых проблем:
1, где заголовок объекта составляет 12 байт Почему? Что объекты конкретных заранее содержат?
А: В нормальных условиях, объект , занимающий первый машинный код составляет 8 байт в 32-битной системе, система 64 представляет собой машинный код занимает 16 байт. Но в моей локальной среде это открыть компрессию ссылка (указатель), так что только 12 байт.
2, вот строки и объект Почему 4 байта?
A: Так как строка или тип объекта, расположение памяти, ссылочные типы, так что его размер с о том , чтобы начать сжатие. Когда сжатие не началась, машина 32 представляет собой тип эталонных 4 байта, 8 байт равен 64, но если в начале сжатия, машина 64 становится ссылочный тип 4 байта.
3, Java должны знать , как читать с 12 по 16 офсет компенсировать это, вместо чтения смещение на 18 или 20?
A: Вот, я думаю, виртуальную машину во время компиляции, он сохранил смещение класса массива VO , что смещение 12 является 16, так что Java не дочитал до 16 лет .

Примечание: Для нормального использования: 

public class Test implements java.io.Serializable {
    //序列化相关
    private static final long serialVersionUID = 6214790243416807050L;

    // JDK里使用的一个工具类对象,提供一些不安全的操作的方法,一般不会在自己的程序中使用该类
    //在这里主要用到其中的objectFieldOffset、putOrderedInt、compareAndSwapInt方法
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    
    //value成员属性的内存地址相对于对象内存地址的偏移量
    private static final long valueOffset;

    static {
      try {
        //初始化valueOffset,通过unsafe.objectFieldOffset方法获取成员属性value内存地址相对于对象内存地址的偏移量
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }
    //int的值,设为volatile,保证线程之间的可见性
    private volatile int value;

    ......
}

 

Опубликовано 136 оригинальных статей · вона похвала 6 · просмотров 1529

рекомендация

отblog.csdn.net/weixin_42073629/article/details/104489155