Java serie multi-threading comprensión --sun.misc.Unsafe

prefacio

clase insegura bajo sun.misc paquete, no es parte del estándar de Java. Pero muchos biblioteca de clases Java Foundation, incluyendo algunas librerías de desarrollo de alto rendimiento son ampliamente utilizados se basan en el desarrollo de la clase en condiciones de riesgo, tales como Netty, Hadoop, Kafka y así sucesivamente.

la memoria del sistema uso inseguro se puede usar para recursos directamente de acceso y la autogestión, jugó un papel importante en la mejora de la eficiencia operativa insegura clase de Java, mejorar la capacidad operativa subyacente lenguaje Java.

Insegura de Java puede ser considerado para ser dejado en la puerta trasera, que ofrece algunas operaciones de bajo nivel, como el acceso directo a la memoria, la programación de subprocesos.

 El funcionario no recomienda el uso de condiciones de riesgo.

 El empleo de las clases

 Obtiene objetos inseguros

Por desgracia, objeto inseguro no puede ser directamente new Unsafe()o vocación Unsafe.getUnsafe()obtener las siguientes razones:

* No directamente new Unsafe(), porque Unsafeestá diseñado para ser ejemplo de realización de modo único, el constructor es privado;

* No llamandoUnsafe.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;
    }

Mientras que no se puede obtener por el método anterior objetos inseguro, pero tienen alguna clase inseguro privado tiene una estáticas propiedades globales theUnsafe(Unsafe实例对象), por la reflexión, el objeto puede ser adquirido atributo de campo theUnsafe el miembro correspondiente a, y dispuesto para ser accesible, en particular para obtener theUnsafe el objeto, el código siguiente: 

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);

    }
}

análisis de código fuente de la clase inseguro 

La mayor parte de la API es inseguro métodos nativos para conseguir llamando al código JNI (JNI: Java Native Interface para la llamada local de Java), incluyendo las siguientes categorías:

1) clase relacionada. El principal proveedor de clase y el método de funcionamiento de sus campos estáticos.

2) en relación a objetos. Objeto Los principales métodos de operación y proporciona sus campos.

3) arrray relacionados. El método de funcionamiento y proporciona los principales elementos de la matriz.

4) en paralelo relevante. Principalmente proporciona primitivas de sincronización de bajo nivel, tales como CAS, la programación de subprocesos,, barreras de memoria volátil.

5) relacionados con la memoria. Proporciona un método de acceso directo a memoria (operación directa de la variante local de la memoria de almacenamiento dinámico de Java), se puede hacer lo mismo que C como el uso gratuito de los recursos de memoria del sistema.

6) Sistema-relacionados. La memoria principal se devuelve un poco de información de bajo nivel, como el tamaño de la dirección, el tamaño de página de memoria.

Una clase para facilitar los Ejemplos posteriores 

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 relacionados 

//静态属性的偏移量,用于在对应的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);

Obtiene el valor de la propiedad de un campo estático 

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

 relacionados 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;

Leer el valor de la instancia de objeto de campo 

//获取实例字段的属性值
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. Matriz relacionados 

/**
 * 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);

Obtener tamaño de la cabeza y el tamaño del elemento de la matriz 

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

ArrayBaseOffset y posicionable por arrayIndexScale cada posición de elemento de la matriz en la memoria. 

4. relacionados Concurrente 

relacionados 4.1CAS

CAS: offset CompareAndSwap, offset memoria de direcciones, el valor esperado era de esperar, el nuevo valor de x. Si el valor es igual a la esperada, tratar de actualizar el valor de la variable x en el valor esperado y la hora actual. Si la actualización se realiza correctamente, devuelve verdadero, de lo contrario, devuelve falso.

//更新变量值为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);

 A partir de Java 8, inseguro proporciona los siguientes métodos:

//增加
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;

programación 4,2 hilo relacionados 

//取消阻塞线程
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 relacionados con la lectura y la escritura

Tipos básicos (boolean, byte, char, short, int, long, float, doble) Java y objeto de referencia en el tipo tiene los siguientes métodos.

//从对象的指定偏移量处获取变量的引用,使用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 barreras relacionadas con la memoria

Java 8 se introduce para definir barreras de memoria, para evitar el reordenamiento código.

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

5. Acceso directo a memoria (memoria no montón)

AllocateMemory memoria asignada manualmente libre (no recogido 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. Sistema-relacionados

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

la extensión del conocimiento

Observamos que hay un procedimiento de los anteriores

  • stateOffset = unsafe.objectFieldOffset (campo) se puede entender a partir del nombre del método: el desplazamiento de la propiedad del objeto adquirido campo del objeto.

Para entender este desplazamiento, es necesario comprender el modelo de memoria de Java

Java modelo de memoria

 

Java objetos almacenados en el diseño de memoria se puede dividir en tres áreas: la cabecera del objeto (Header), datos de instancia (instancia de datos) y el relleno de alineación (relleno), la comprensión sencilla:

  • cabecera del objeto , lo que es un objeto?
  • datos de instancia , qué objetos hay?
  • relleno de alineación , no es crítica, el propósito es lograr un múltiplo de 8 bits acolchada.

Como un simple ejemplo, las siguientes categorías:

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

 VO vo = new VO (); cuando, Java abrió a una dirección de memoria, la cabecera de objeto contiene una longitud fija (que se supone ser de 16 bytes, los bits son diferentes máquinas / compresión de cabecera objeto afectará a la longitud de la cabecera del objeto) + ejemplos de datos (a + 4 bytes B 4 bytes) + relleno.

Dicha conclusión directa aquí, hemos dicho más arriba, el desplazamiento está configurado aquí como una propiedad de la desviación es superior a 16, el desplazamiento es de 20 propiedades b.

En inseguros dentro de la clase, hemos encontrado un método unsafe.getInt (objeto, offset);
por unsafe.getInt (vo, 16) pueden obtenerse en el valor vo.a. No se piense reflejado? De hecho, lo que refleja la subyacente Java ES SEGURO usados

Una reflexión más profunda

¿Cómo sé que compensó una clase para cada propiedad? Sólo la cantidad de desplazamiento, java ¿cómo se sabe dónde leído hasta ahora es el valor de esta propiedad?

Ver propiedad compensado recomendar una herramienta como jol: http://openjdk.java.net/projects/code-tools/jol/
con jol puede ver fácilmente el diseño de memoria de Java, en combinación con el código de explicar

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);
    }

En mi máquina local resultados de la prueba son los siguientes: 

# 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

 En los resultados, se encontró que:

  • 1, mi entorno de máquina virtual local es abierto y la compresión de 64 bits comprimido, los objetos son alineación de 8 bytes
  • 2, la cabecera VO memoria Disposición objeto contiene un 12-byte, los datos int de 4 bytes, los datos de 8 bytes de largo, y el otro objeto de cadena es de 4 bytes, 4 bytes finalmente alineadas.
  • 3, el orden es la capa de memoria incompatible propiedad de clase VO con la declaración de propiedad.
  • 4, atributos estáticos de la distribución de la memoria VO VO clase no es, porque pertenece a la clase de clase.
  • 5, clase VO puede ser determinada por el número de bytes ocupados por un objeto, este espacio se ha determinado en tiempo de compilación (Nota: Este espacio no es un objeto espacial real).
  • 6, como se describe anteriormente, 12 pueden ser leídos por un valor de desplazamiento 2 se almacena aquí.

Sal de nuevos problemas:
1, donde la cabecera del objeto es de 12 bytes ¿Por qué? ¿Cuáles son los objetos específicos de antelación contienen?
A: En circunstancias normales, un objeto que ocupa un primer código de máquina es de 8 bytes en el sistema de 32 bits, el sistema 64 es un código de máquina ocupa 16 bytes. Pero en mi entorno local que es abrir una compresión de referencia (puntero), por lo que sólo 12 bytes.
2, aquí está cuerdas y objetos Por qué es de 4 bytes?
R: Debido a que el tipo de cadena o de objetos, la capa de memoria, son los tipos de referencia, por lo que su tamaño con sobre si se debe iniciar la compresión. Cuando no se inicia la compresión, la máquina 32 es un tipo de referencia 4 bytes, 8 bytes es 64, pero si el inicio de compresión, la máquina 64 se convierte en un tipo de referencia 4 bytes.
3, Java debe saber cómo leer del 12 al 16 de desplazamiento de compensación que, en lugar de leer el desplazamiento a 18 o 20?
R: Aquí, supongo, la máquina virtual en tiempo de compilación, se ha conservado el desplazamiento de la clase Array VO que compensado de 12 es 16, por lo que Java leer hasta 16.

Nota: Para el uso normal: 

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;

    ......
}

 

Publicados 136 artículos originales · ganado elogios 6 · vistas 1529

Supongo que te gusta

Origin blog.csdn.net/weixin_42073629/article/details/104489155
Recomendado
Clasificación