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

    }
}

安全でないクラスのソースコード解析 

次のカテゴリを含む:(JAVAローカルコール用のJava Native InterfaceのJNI)、APIのほとんどは、JNIコードを呼び出すことによって、達成するためのネイティブ危険な方法であります:

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の新しい値を期待しました。値が予想されるに等しい場合、期待値と現在の時間における変数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);

 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型、長い、float型、double)の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(フィールド)メソッドの名前から理解することができる:オブジェクトの取得されたオブジェクトプロパティフィールドのオフセット。

このオフセットを理解するには、Javaのメモリモデルを理解する必要があります

Javaのメモリモデル

 

オブジェクトヘッダ(ヘッダ)、インスタンスデータ(インスタンスデータ)とアライメントパディング(パディング)、単純な理解:Javaがメモリ・レイアウトに記憶されたオブジェクトは、3つの領域に分割することができます。

  • オブジェクトヘッダ、オブジェクトは何ですか?
  • インスタンス・データ、何があっオブジェクト?
  • アライメントパディングは、目的が8ビットパディングの倍数を達成することである、重要ではありません。

簡単な例として、次のカテゴリ:

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

 VO、VO =新しいVO()、Java(登録商標)はメモリアドレスに開いたときに、オブジェクトヘッダは固定長(オブジェクトヘッダ圧縮は、オブジェクトヘッダ長に影響を与える/ビットは異なるマシンであり、16バイトであると仮定)+を含有しますデータの例(4つのバイトB + 4バイト)+パディング。

オフセット20個のbプロパティで、オフセットのプロパティが16を超えているとして、ここで具体化されているオフセットは、ここでの直接の結論は、我々は上記の特徴とします。

クラス内の危険において、我々は方法unsafe.getInt(オブジェクト、オフセット)を発見した。
unsafe.getInt(VO、16)によってvo.a値を得ることができます。反映だと思いませんか?実際には、背後のjavaを反映しUNSAFE使用されています

また、反射

私は、各プロパティのクラスを相殺している方法を知っていますか?これまでに読み取るためには、このプロパティの値ですだけオフセット量、Javaはどのように知っていますか?

:Viewプロパティは、JOLのようなツールをお勧めしますオフセットhttp://openjdk.java.net/projects/code-tools/jol/を
説明し、簡単にジャワのメモリレイアウトを表示することができ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クラスのプロパティです。
  • 彼はクラスのクラスに属しているため4、クラスVO VOのメモリレイアウトの静的な属性は、ありません。
  • (この空間は、実空間のオブジェクトではないことに注意してください)5、VOクラスは、バイトの数によって決定することができるオブジェクトによって占め、この空間は、コンパイル時に決定されています。
  • 上述したように図6に示すように、図12は、ここに格納されたオフセット値2によって読み取ることができます。

:新たな問題から出てくる
1、オブジェクトヘッダはなぜバイト12のですか?事前に特定のオブジェクトは何が含まれていますか?
A:通常の状況下では、第1の機械コードを占めるオブジェクトは、システム64は、機械コードは16バイトを占有している、32ビットシステムでは8バイトです。しかし、私の地元の環境では、参照(ポインタ)の圧縮を開くには、これだけ12バイトです。
2、ここでのStringとObjectのはなぜ4バイトですか?
A:文字列またはオブジェクトタイプ、メモリレイアウト、参照型であるため、圧縮を開始するかどうかについての彼のサイズので。圧縮が開始されていない場合、機械32は、基準の種類4バイトで、8つのバイト64が、圧縮開始する場合、機械64は、4バイトを参照型となります。
3、Javaは16の代わりに18または20にオフセットを読んで、それを相殺相殺するために12から読み取る方法を知っている必要がありますか?
:ここでは、私はコンパイル時に、仮想マシンを推測、それはJavaが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