자바 멀티 스레딩 시리즈 --sun.misc.Unsafe 이해

머리말

sun.misc 패키지에서 안전하지 않은 클래스, 자바 표준의 일부. 그러나 널리 사용되는 일부 고성능 개발 라이브러리를 포함하여 많은 자바 기반 클래스 라이브러리 등의 Netty, 하둡, 카프카 등 등 안전하지 않은 수준의 개발을 기반으로합니다.

안전하지 않은 사용 시스템 메모리를 직접 액세스 자원과 자기 관리에 사용되는 안전하지 않은 클래스 자바 운영 효율 향상에 중요한 역할을 할 수있다, 자바 언어를 기본 운영 능력을 향상시킬 수 있습니다.

안전하지 않은 자바가 다시 문에 남아있는 것으로 간주 될 수있다, 이러한 직접 메모리 액세스, 스레드 스케줄링과 같은 일부 낮은 수준의 작업을 제공합니다.

 이 관리는 안전하지 않은 사용하지 않는 것이 좋습니다.

 클래스의 사용

 안전하지 않은 객체를 가져옵니다

불행하게도, 안전하지 않은 객체는 직접 수 없습니다 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);

    }
}

안전하지 않은 클래스 소스 코드 분석 

다음과 같은 범주를 포함 : (자바 시내 전화를위한 자바 네이티브 인터페이스 JNI)는 API의 대부분은 JNI 코드를 호출하여 달성하기 위해 기본 안전하지 않은 방법입니다 :

1) 클래스 관련. 정적 필드의 동작의 주요 클래스의 제공 및 방법에 관한 것이다.

2) 객체 관련. 작업의 주요 방법을 개체와 개체의 필드를 제공합니다.

3) 관계를 Arrray. 동작 방법은 기본 배열 요소를 제공한다.

4) 관련 동시 접속. 주로 CAS, 스레드 스케줄링, 휘발성 메모리 장벽과 같은 낮은 수준의 동기화 프리미티브를 제공합니다.

5) 메모리 관련. 직접 메모리 액세스 방법 (로컬 우회 자바 힙 메모리를 직접 운전)를 제공합니다, 당신은 시스템 메모리 리소스의 무료 사용과 같은 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()));

arrayIndexScale하여 메모리 어레이의 각 요소의 위치 및 위치 될 ArrayBaseOffset. 

4. 동시 관련 

4.1CAS 관련

CAS : X의 CompareAndSwap, 메모리 오프셋 어드레스 오프셋 기대치 예상 새 값. 값이 예상과 같은 경우, 기대 값의 변수 x와 현재 시간의 값을 업데이트하려고합니다. 업데이트가 성공하면 true를 반환하고 그렇지 않으면 false를 반환합니다.

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

 자바 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는, 긴, 플로트, 더블) 유형의 자바와 객체 참조는 다음과 같은 방법이있다.

//从对象的指定偏移量处获取变量的引用,使用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 메모리 관련 장애

자바 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 (필드)에있어서의 이름에서 알 수있다 : 상기 객체의 획득 객체 속성 필드의 오프셋.

이 오프셋을 이해하려면 자바 메모리 모델을 이해하는 데 필요한

자바 메모리 모델

 

객체 헤더 (헤더) 인스턴스 데이터 (예 : 데이터)와 정렬 심 (패딩), 간단한 이해 : 자바는 메모리 레이아웃에 저장된 객체는 세 영역으로 분할 될 수있다 :

  • 개체 헤더 객체는 무엇인가?
  • 인스턴스 데이터 , 무슨 일이 객체?
  • 정렬 패딩 의 목적은 8 비트의 배수 패딩 달성하는, 중요하지 않다.

간단한 예를 들어, 카테고리를 다음과 같은 :

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

 VO VO = 새로운 VO는 (), Java는 메모리 어드레스를 열 때, 오브젝트 헤더는 고정 길이 포함 + (16 바이트라고 가정을 비트는 다른 기계 / 개체 헤더 압축은 오브젝트 헤더 길이에 영향을 미칠 것이다된다) 데이터의 예 (4 바이트 B A + 4 바이트) + 심.

(가)는 오프셋 (20 개) B의 특성이고, 오프셋의 특성 (16) 위에 여기 구현 오프셋 여기 직접적인 결론은, 위에서 상기.

클래스 내부 안전에서는 방법 unsafe.getInt (오프셋 객체) 발견;
unsafe.getInt (VO, 16)에 의해 vo.a 값을 얻을 수있다. 반사 생각 아닌가요? 실제로, 기본 자바 반영하여 사용 불안전

또한 반사

어떻게 각 속성 클래스를 오프셋 (offset) 알 수 있습니까? 만 오프셋 금액은 자바가 어떻게 지금까지 어디에서 읽어 알고이 속성의 값입니다합니까?

:보기 속성은 욜 같은 도구 추천 오프셋 http://openjdk.java.net/projects/code-tools/jol/
설명 코드와 함께 쉽게 자바의 메모리 레이아웃을 볼 수 있습니다 욜과를,

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 VO 클래스 메모리 레이아웃 객체 헤더는 12 바이트, 4 바이트 int 데이터는 8 데이터 바이트 길이 포함하고, 다른 문자열 객체는 4 바이트, 마지막 정렬 4 바이트이다.
  • 도 3에서, 순서는 속성 선언과 일치 메모리 레이아웃 VO 클래스 속성이다.
  • 그는 클래스 클래스에 속하기 때문에 클래스 VO VO 메모리 레이아웃의 4, 정적 속성이 아니다.
  • (이 공간은 실제 공간 객체가 아닌 주) (5), VO 클래스는 객체가 차지하는 바이트 수에 의해 결정될 수있다,이 공간은 컴파일시에 결정되었다.
  • 상술 한 바와 같이도 6에서, 12은 여기에 저장된 오프셋 값 (2)에 의해 판독 될 수있다.

: 새로운 문제로 나와
1, 객체 헤더는 왜 바이트 12? 사전에 특정 객체는 무엇을 포함하고 있습니다?
A는 정상 상황에서는, 제 기계어 차지하는 물체가 32 비트 시스템에 8 바이트이며, 시스템 (64)은 머신 코드가 16 바이트를 차지하고있다. 하지만 내 로컬 환경에서는 참조 (포인터) 압축을 열 수 있도록 단지 12 바이트입니다.
2, 여기에 문자열이고 개체 이유는 4 바이트?
A : 문자열 또는 개체 유형, 메모리 레이아웃, 참조 유형은, 자신의 크기에 대한 여부와 압축을 시작하기 때문에 때문에. 압축이 시작되지 않을 때, 컴퓨터 (32)는 8 바이트가 64이고 문헌 4 바이트 타입이지만, 압축 시작하면, 시스템 (64)은 기준 입력 4 바이트가된다.
3, 자바는 16 대신에 18 또는 20 오프셋 읽기, 그것은 오프셋 상쇄 (12)로부터 읽는 방법을 알아야합니까?
A는 : 여기, 내가 컴파일시에, 가상 머신을 생각, 그것은 자바 16까지 읽을 수 있도록 (12)의 오프셋 오프셋 배열 VO 클래스, 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