Java中的Unsafe

簡介

Java是一種安全的編程語言,可以防止程序員犯許多愚蠢的錯誤河爹,其中大多數(shù)錯誤都是基于內(nèi)存管理的匠璧。但是,有一種方法可以繞過這些限制咸这,即使用 Unsafe class夷恍。可以手動操作內(nèi)存媳维,這樣可以大大減少垃圾回收時間而且可以減少堆內(nèi)內(nèi)存的使用酿雪。

獲取Unsafe對象

Unsafe類里面可以看到有一個getUnsafe方法:

    @CallerSensitive
public static Unsafe getUnsafe() {
  Class<?> caller = Reflection.getCallerClass();
  if (!VM.isSystemDomainLoader(caller.getClassLoader()))
      throw new SecurityException("Unsafe");
  return theUnsafe;
}

但是直接調(diào)用會拋出SecurityException不安全異常,因為這個getUnsafe方法會檢查我們的代碼是否由BootClassLoader加載了侄刽。很明顯項目中所寫的代碼都是由Appclass Loader加載的指黎。所以報錯了。
如何做呢州丹?
(1)我們可以使我們的代碼“可信”醋安。在運行程序時使用選項bootclasspath,把要使用Unsafe的類添加到系統(tǒng)類路徑中墓毒。
例如:

java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.duoheshui.com.UnsafeTestClient

但是這樣太麻煩了
(2)使用反射:

    public static Unsafe getUnsafe() {
        try {
            Field singletonInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singletonInstanceField.setAccessible(true);
            return (Unsafe) singletonInstanceField.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
Unsafe類的成員

除了上面談及的getUnsafe會返回Unsafe實例theUnsafe外吓揪,Unsafe一共由105個方法組成,大部分都是native方法所计。下面是一些可能用到的方法:

返回低級別內(nèi)存信息

addressSize()
pageSize()

手動獲得對象和對象方法

allocateInstance() 避開構(gòu)造方法生成對象
objectFieldOffset() 獲得對象的某個成員的地址偏移量

手動獲得類或者靜態(tài)成員

staticFieldOffset() 獲得某個靜態(tài)成員的地址偏移量
defineClass()
defineAnonymousClass()
ensureClassInitialized()

手動獲得數(shù)組

arrayBaseOffset()
arrayIndexScale()

同步的低級別基本方法

monitorEnter()
tryMonitorEnter()
monitorExit()
compareAndSwapInt()
putOrderedInt()

手動操作內(nèi)存

allocateMemory()
copyMemory()
freeMemory()
getAddress()
getInt() 柠辞,getInt(Object var1, long var2)第一個參數(shù)是要get的對象,第二個參數(shù)是字段的偏移量
putInt()

阻塞和喚醒

pack()
unpack()

用法

1主胧、CAS(compareAndSwap)

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
           //獲取變量value的地址偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //聲明為volatile叭首,有變化回寫到主內(nèi)存习勤,其他線程再重新從主內(nèi)存讀取最新的數(shù)據(jù),保持可見性
    private volatile int value;
    //比較并交換
     public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

sun.misc.Unsafe.compareAndSwapInt(Object, long, int, int)


2焙格、避開構(gòu)造方法初始化對象图毕,使用allocateInstance

        Unsafe unsafe = getUnsafe();
        final Class userClass = User.class;
        User user = (User) unsafe.allocateInstance(userClass );

3、修改對象成員值眷唉,使用putInt()

        User a = new User(10);
        Field f = User.class.getDeclaredField("age");
        unsafe.putInt(a, unsafe.objectFieldOffset(f), 8);

4吴旋、獲取對象地址
獲取部門對象在內(nèi)存中的地址偏移量

private DepartMent dept;
valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("dept"));
CAS(compareAndSwapInt)源碼實現(xiàn)

JDK8 src/share/vm/prims/unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

將調(diào)用Atomic::cmpxchg(x, addr, e)進行對比交換,該方法在hotspot\src\share\vm\runtime\atomic.cpp中

inline jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest,
jbyte compare_value, cmpxchg_memory_order order) {
    STATIC_ASSERT(sizeof(jbyte) == 1);
    volatile jint* dest_int =
    static_cast<volatile jint*>(align_ptr_down(dest, sizeof(jint)));
    size_t offset = pointer_delta(dest, dest_int, 1);
    jint cur = *dest_int;
    jbyte* cur_as_bytes = reinterpret_cast<jbyte*>(&cur);
    // current value may not be what we are looking for, so force it
    // to that value so the initial cmpxchg will fail if it is different
    cur_as_bytes[offset] = compare_value;
    // always execute a real cmpxchg so that we get the required memory
    // barriers even on initial failure
    do {
        // value to swap in matches current value ...
        jint new_value = cur;
        // ... except for the one jbyte we want to update
        reinterpret_cast<jbyte*>(&new_value)[offset] = exchange_value;
        jint res = cmpxchg(new_value, dest_int, cur, order);
        if (res == cur) break; // success
        // at least one jbyte in the jint changed value, so update
        // our view of the current jint
        cur = res;
        // if our jbyte is still as cur we loop and try again
    } while (cur_as_bytes[offset] == compare_value);
    return cur_as_bytes[offset];
}

大意就是先去獲取一次結(jié)果厢破,如果結(jié)果和現(xiàn)在不同,就直接返回治拿,因為有其他人修改了摩泪;否則會一直嘗試去修改。直到成功劫谅。

參考

http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末见坑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捏检,更是在濱河造成了極大的恐慌荞驴,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贯城,死亡現(xiàn)場離奇詭異熊楼,居然都是意外死亡,警方通過查閱死者的電腦和手機能犯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門鲫骗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人踩晶,你說我怎么就攤上這事执泰。” “怎么了渡蜻?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵术吝,是天一觀的道長。 經(jīng)常有香客問我茸苇,道長排苍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任税弃,我火速辦了婚禮纪岁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘则果。我一直安慰自己幔翰,他們只是感情好漩氨,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遗增,像睡著了一般叫惊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上做修,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天霍狰,我揣著相機與錄音,去河邊找鬼饰及。 笑死蔗坯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的燎含。 我是一名探鬼主播宾濒,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屏箍!你這毒婦竟也來了绘梦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤赴魁,失蹤者是張志新(化名)和其女友劉穎卸奉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颖御,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡榄棵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了郎嫁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秉继。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泽铛,靈堂內(nèi)的尸體忽然破棺而出尚辑,到底是詐尸還是另有隱情,我是刑警寧澤盔腔,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布杠茬,位于F島的核電站,受9級特大地震影響弛随,放射性物質(zhì)發(fā)生泄漏瓢喉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一舀透、第九天 我趴在偏房一處隱蔽的房頂上張望栓票。 院中可真熱鬧,春花似錦、人聲如沸走贪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坠狡。三九已至继找,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逃沿,已是汗流浹背婴渡。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凯亮,地道東北人边臼。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像假消,于是被迫代替她去往敵國和親硼瓣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容