日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java 中random类使用_Java中的天使和魔鬼:Unsafe类

發(fā)布時間:2024/1/23 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 中random类使用_Java中的天使和魔鬼:Unsafe类 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

我們在看ConcurrentHashMap源碼時經(jīng)常看到Unsafe類的使用,今天我們來了解下Unsafe類。

Java是一個安全的編程語言,它能最大程度的防止程序員犯一些低級的錯誤(大部分是和內(nèi)存管理有關(guān)的)。但凡事不是絕對的,使用Unsafe程序員就可以操作內(nèi)存,因此可能帶來一個安全隱患。

這篇文章是就快速學(xué)習(xí)下sun.misc.Unsafe的公共API和一些有趣的使用例子。

1、Unsafe 實例化

在使用Unsafe之前我們需要先實例化它。但我們不能通過像Unsafe unsafe = new Unsafe()這種簡單的方式來實現(xiàn)Unsafe的實例化,這是由于Unsafe的構(gòu)造方法是私有的。Unsafe有一個靜態(tài)的getUnsafe()方法,但是如果天真的以為調(diào)用該方法就可以的話,那你將遇到一個SecurityException異常,這是由于該方法只能在被信任的代碼中調(diào)用。

public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null) throw new SecurityException("Unsafe"); return theUnsafe;}

那Java是如何判斷我們的代碼是否是受信的呢?它就是通過判斷加載我們代碼的類加載器是否是根類加載器。

我們可是通過這種方法將我們自己的代碼變?yōu)槭苄诺?#xff0c;使用jvm參數(shù)bootclasspath。如下所示:

java?-Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:.?com.mishadoff.magic.UnsafeClient

但這種方式太難了,Unsafe類內(nèi)部有一個名為theUnsafe的私有實例變量,我們可以通過反射來獲取該實例變量。

Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe?unsafe?=?(Unsafe)?f.get(null);

注意: 忽略你的IDE提示. 例如, eclipse可能會報這樣的錯誤”Access restriction…” 單如果你運行你的代碼,會發(fā)現(xiàn)一切正常。如果還是還是提示錯誤,你可以通過如下的方式關(guān)閉該錯誤提示:

Preferences -> Java -> Compiler -> Errors/Warnings ->Deprecated?and?restricted?API?->?Forbidden?reference?->?Warning?????

2、Unsafe API

類 sun.misc.Unsafe 由150個方法組成。事實上這些方法只有幾組是非常重要的用來操作不同的對象。下面我們就來看下這些方法中的一部分。

1、Info 僅僅是返回一個低級別的內(nèi)存相關(guān)的信息
addressSize
pageSize

2、Objects. 提供操作對象和對象字段的方法
allocateInstance
objectFieldOffset

3、Classes. 提供針對類和類的靜態(tài)字段操作的方法
staticFieldOffset
defineClass
defineAnonymousClass
ensureClassInitialized

4、Arrays. 數(shù)組操作
arrayBaseOffset
arrayIndexScale

5、Synchronization. 低級別的同步原語
monitorEnter
tryMonitorEnter
monitorExit
compareAndSwapInt
putOrderedInt

6、Memory. 直接訪問內(nèi)存的方法
allocateMemory
copyMemory
freeMemory
getAddress
getInt
putInt

接下來是一些有趣的使用case

3、跳過構(gòu)造初始化

allocateInstance方法可能是有用的,當你需要在構(gòu)造函數(shù)中跳過對象初始化階段或繞過安全檢查又或者你想要實例化哪些沒有提供公共構(gòu)造函數(shù)的類時就可以使用該方法。考慮下面的類:

class A { private long a; // not initialized value public A() { this.a = 1; // initialization } public long a() { return this.a; }}

通過構(gòu)造函數(shù),反射,Unsafe分別來實例化該類結(jié)果是不同的:

A o1 = new A(); // constructoro1.a(); // prints 1A o2 = A.class.newInstance(); // reflectiono2.a(); // prints 1A o3 = (A) unsafe.allocateInstance(A.class); // unsafeo3.a();?//?prints?0

allocateInstance根本沒有進入構(gòu)造方法,對于單例模式,簡直是噩夢。

4、內(nèi)存修改,繞過安全檢查器

對C程序員來說這中情況是很常見的。

思考一下一些簡單的類是如何堅持訪問規(guī)則的:

class Guard { private int ACCESS_ALLOWED = 1; public boolean giveAccess() { return 42 == ACCESS_ALLOWED; }}

客戶端代碼是非常安全的,調(diào)用giveAccess()檢查訪問規(guī)則。不幸的是對所有的客戶端代碼,它總是返回false。只有特權(quán)用戶在某種程度上可以改變ACCESS_ALLOWED常量并且獲得訪問權(quán)限。

事實上,這不是真的。這是證明它的代碼:

Guard guard = new Guard();guard.giveAccess(); // false, no access// bypassUnsafe unsafe = getUnsafe();Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruptionguard.giveAccess();?//?true,?access?granted

現(xiàn)在所有的客戶端都沒有訪問限制了。

事實上同樣的功能也可以通過反射來實現(xiàn)。但有趣的是, 通過上面的方式我們修改任何對象,即使我們沒有持有對象的引用。

舉個例子, 在內(nèi)存中有另外的一個Guard對象,并且地址緊挨著當前對象的地址,我們就可以通過下面的代碼來修改該對象的ACCESS_ALLOWED字段的值。

unsafe.putInt(guard,?16?+?unsafe.objectFieldOffset(f),?42);?//?memory?corruption

注意,我們沒有使用任何指向該對象的引用,16是Guard對象在32位架構(gòu)上的大小。我們也可以通過sizeOf方法來計算Guard對象的大小。

5、sizeOf 計算內(nèi)存大小

使用objectFieldOffset方法我們可以實現(xiàn)C風(fēng)格的sizeof方法。下面的方法實現(xiàn)返回對象的表面上的大小

public static long sizeOf(Object o) { Unsafe u = getUnsafe(); HashSet fields = new HashSet(); Class c = o.getClass(); while (c != Object.class) { for (Field f : c.getDeclaredFields()) { if ((f.getModifiers() & Modifier.STATIC) == 0) { fields.add(f); } } c = c.getSuperclass(); } // get offset long maxSize = 0; for (Field f : fields) { long offset = u.objectFieldOffset(f); if (offset > maxSize) { maxSize = offset; } } return ((maxSize/8) + 1) * 8; // padding}

算法邏輯如下:收集所有包括父類在內(nèi)的非靜態(tài)字段,獲得每個字段的偏移量,發(fā)現(xiàn)最大并添加填充。也許,我錯過了一些東西,但是概念是明確的。

更簡單的sizeof方法實現(xiàn)邏輯是:我們只讀取該對象對應(yīng)的class對象中關(guān)于大小的字段值。在JVM 1.7 32 位版本上該表示大小的字段偏移量是12。

public static long sizeOf(Object object){ return getUnsafe().getAddress( normalize(getUnsafe().getInt(object, 4L)) + 12L);}

normalize是一個將有符號的int類型轉(zhuǎn)為無符號的long類型的方法。

private static long normalize(int value) { if(value >= 0) return value; return (~0L >>> 32) & value;}

太棒了,這個方法返回的結(jié)果和我們之前的sizeof函數(shù)是相同的。

事實上,對于合適的,安全的,準確的sizeof函數(shù)最好使用java.lang.instrument包,但它需要特殊的JVM參數(shù)。

6、淺拷貝

在實現(xiàn)了計算對象淺層大小的基礎(chǔ)上,我們可以非常容易的添加對象的拷貝方法。標準的辦法需要修改我們的代碼和Cloneable。或者你可以實現(xiàn)自定義的對象拷貝函數(shù),但它不會變?yōu)橥ㄓ玫暮瘮?shù)。

淺拷貝:

static Object shallowCopy(Object obj) { long size = sizeOf(obj); long start = toAddress(obj); long address = getUnsafe().allocateMemory(size); getUnsafe().copyMemory(start, address, size); return fromAddress(address);}

toAddress 和 fromAddress 將對象轉(zhuǎn)為它在內(nèi)存中的地址或者從指定的地址內(nèi)容轉(zhuǎn)為對象。

static long toAddress(Object obj) { Object[] array = new Object[] {obj}; long baseOffset = getUnsafe().arrayBaseOffset(Object[].class); return normalize(getUnsafe().getInt(array, baseOffset));}static Object fromAddress(long address) { Object[] array = new Object[] {null}; long baseOffset = getUnsafe().arrayBaseOffset(Object[].class); getUnsafe().putLong(array, baseOffset, address); return array[0];}

該拷貝函數(shù)可以用來拷貝任何類型的對象,因為對象的大小是動態(tài)計算的。

注意 在完成拷貝動作后你需要將拷貝對象的類型強轉(zhuǎn)為目標類型。

7、隱藏密碼

在Unsafe的直接內(nèi)存訪問方法使用case中有一個非常有趣的用法就是刪除內(nèi)存中不想要的對象。

大多數(shù)獲取用戶密碼的API方法的返回值不是byte[]就是char[],這是為什么呢?

這完全是出于安全原因, 因為我們可以在不需要它們的時候?qū)?shù)組元素置為失效。如果我們獲取的密碼是字符串類型,則密碼字符串是作為一個對象保存在內(nèi)存中的。要將該密碼字符串置為無效,我們只能講字符串引用職位null,但是該字符串的內(nèi)容任然存在內(nèi)存直到GC回收該對象后。

這個技巧在內(nèi)存創(chuàng)建一個假的大小相同字符串對象來替換原來的:

String password = new String("l00k@myHor$e");String fake = new String(password.replaceAll(".", "?"));System.out.println(password); // l00k@myHor$eSystem.out.println(fake); // ????????????getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));System.out.println(password); // ????????????System.out.println(fake);?//?????????????

感覺安全了嗎?

其實該方法不是真的安全。想要真的安全我們可以通過反射API將字符串對象中的字符數(shù)組value字段的值修改為null。

Field stringValue = String.class.getDeclaredField("value");stringValue.setAccessible(true);char[] mem = (char[]) stringValue.get(password);for (int i=0; i < mem.length; i++) { mem[i] = '?';}

8、多重繼承

在Java中本來是沒有多重集成的。除非我們可以將任意的類型轉(zhuǎn)為我們想要的任意類型。

long intClassAddress = normalize(getUnsafe().getInt(new Integer(0), 4L));long strClassAddress = normalize(getUnsafe().getInt("", 4L));getUnsafe().putAddress(intClassAddress?+?36,?strClassAddress);

這段代碼將String類添加到Integer的超類集合中,所以我們的強轉(zhuǎn)代碼是沒有運行時異常的。

(String)?(Object)?(new?Integer(666))

有個問題是我們需要先將要轉(zhuǎn)的對象轉(zhuǎn)為Object,然后再轉(zhuǎn)為我們想要的類型。這是為了欺騙編譯器。

9、動態(tài)類

我們可以在運行時創(chuàng)建類, 例如通過一個編譯好的class文件。將class文件的內(nèi)容讀入到字節(jié)數(shù)組中然后將該數(shù)組傳遞到合適的defineClass方法中。

byte[] classContents = getClassContent();Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);c.getMethod("a").invoke(c.newInstance(),?null);?//?1

讀取class文件內(nèi)如的代碼:

private static byte[] getClassContent() throws Exception { File f = new File("/home/mishadoff/tmp/A.class"); FileInputStream input = new FileInputStream(f); byte[] content = new byte[(int)f.length()]; input.read(content); input.close(); return content;}

該方式是非常有用的,如果你確實需要在運行時動態(tài)的創(chuàng)建類。比如生產(chǎn)代理類或切面類。

10、拋出一個異常

不喜歡受檢異常?這不是問題。

getUnsafe().throwException(new?IOException());

該方法拋出一個受檢異常,但是你的代碼不需要強制捕獲該異常就像運行時異常一樣。

11、快速序列化

每個人都知道java標準的序列化的功能速度很慢而且它還需要類擁有公有的構(gòu)造函數(shù)。

外部序列化是更好的方式,但是需要定義針對待序列化類的schema。

非常流行的高性能序列化庫,像kryo是有使用限制的,比如在內(nèi)存缺乏的環(huán)境就不合適。

但通過使用Unsafe類我們可以非常簡單的實現(xiàn)完整的序列化功能。

序列化:
通過反射定義類的序列化。這個可以只做一次。
通過Unsafe的getLong, getInt, getObject等方法獲取字段真實的值。
添加可以恢復(fù)該對象的標識符。
將這些數(shù)據(jù)寫入到輸出
當然也可以使用壓縮來節(jié)省空間。

反序列化:
創(chuàng)建一個序列化類的實例,可以通過方法allocateInstance。因為該方法不需要任何構(gòu)造方法。
創(chuàng)建schema, 和序列化類似
從文件或輸入讀取或有的字段
使用 Unsafe 的 putLong, putInt, putObject等方法來填充對象。

事實上要正確實現(xiàn)序列化和反序列化需要注意很多細節(jié),但是思路是清晰的。
這種序列化方式是非常快的。

12、大數(shù)組

如你所知Java數(shù)組長度的最大值是Integer.MAX_VALUE。使用直接內(nèi)存分配我們可以創(chuàng)建非常大的數(shù)組,該數(shù)組的大小只受限于堆的大小。

這里有一個SuperArray的實現(xiàn):

class SuperArray { private final static int BYTE = 1; private long size; private long address; public SuperArray(long size) { this.size = size; address = getUnsafe().allocateMemory(size * BYTE); } public void set(long i, byte value) { getUnsafe().putByte(address + i * BYTE, value); } public int get(long idx) { return getUnsafe().getByte(address + idx * BYTE); } public long size() { return size; }}

一個簡單的用法:

long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;SuperArray array = new SuperArray(SUPER_SIZE);System.out.println("Array size:" + array.size()); // 4294967294for (int i = 0; i < 100; i++) { array.set((long)Integer.MAX_VALUE + i, (byte)3); sum += array.get((long)Integer.MAX_VALUE + i);}System.out.println("Sum?of?100?elements:"?+?sum);??//?300

事實上該技術(shù)使用了非堆內(nèi)存off-heap memory,在 java.nio 包中也有使用。

通過這種方式分配的內(nèi)存不在堆上,并且不受GC管理。因此需要小心使用Unsafe.freeMemory()。該方法不會做任何邊界檢查,因此任何不合法的訪問可能就會導(dǎo)致JVM崩潰。

這種使用方式對于數(shù)學(xué)計算是非常有用的,因為代碼可以操作非常大的數(shù)據(jù)數(shù)組。同樣的編寫實時程序的程序員對此也非常感興趣,因為不受GC限制,就不會因為GC導(dǎo)致非常大的停頓。

13、并發(fā)

關(guān)于并發(fā)編程使用Unsafe的只言片語。compareAndSwap 方法是原子的,可以用來實現(xiàn)高性能的無鎖化數(shù)據(jù)結(jié)構(gòu)。

舉個例子,多個線程并發(fā)的更新共享的對象這種場景:

首先我們定義一個簡單的接口 Counter:

interface Counter { void increment(); long getCounter();}

我們定義工作線程 CounterClient, 它會使用 Counter:

class CounterClient implements Runnable { private Counter c; private int num; public CounterClient(Counter c, int num) { this.c = c; this.num = num; } @Override public void run() { for (int i = 0; i < num; i++) { c.increment(); } }}

這是測試代碼:

int NUM_OF_THREADS = 1000;int NUM_OF_INCREMENTS = 100000;ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);Counter counter = ... // creating instance of specific counterlong before = System.currentTimeMillis();for (int i = 0; i < NUM_OF_THREADS; i++) { service.submit(new CounterClient(counter, NUM_OF_INCREMENTS));}service.shutdown();service.awaitTermination(1, TimeUnit.MINUTES);long after = System.currentTimeMillis();System.out.println("Counter result: " + c.getCounter());System.out.println("Time?passed?in?ms:"?+?(after?-?before));

第一個實現(xiàn)-沒有同步的計數(shù)器:

class StupidCounter implements Counter { private long counter = 0; @Override public void increment() { counter++; } @Override public long getCounter() { return counter; }}

輸出:

Counter result: 99542945Time?passed?in?ms:?679

速度很多,但是沒有對所有的線程進行協(xié)調(diào)所以結(jié)果是錯誤的。第二個版本,使用Java常見的同步方式來實現(xiàn)

class SyncCounter implements Counter { private long counter = 0; @Override public synchronized void increment() { counter++; } @Override public long getCounter() { return counter; }}

輸出:

Counter result: 100000000Time?passed?in?ms:?10136

徹底的同步當然會導(dǎo)致正確的結(jié)果。但是花費的時間令人沮喪。讓我們試試 ReentrantReadWriteLock:

class LockCounter implements Counter { private long counter = 0; private WriteLock lock = new ReentrantReadWriteLock().writeLock(); @Override public void increment() { lock.lock(); counter++; lock.unlock(); } @Override public long getCounter() { return counter; }}

輸出:

Counter result: 100000000Time?passed?in?ms:?8065

結(jié)果依然是正確的,時間也短。那使用原子的類呢?

class AtomicCounter implements Counter { AtomicLong counter = new AtomicLong(0); @Override public void increment() { counter.incrementAndGet(); } @Override public long getCounter() { return counter.get(); }}

輸出:

Counter result: 100000000Time?passed?in?ms:?6552

使用AtomicCounter的效果更好一點。最后我們試試Unsafe的原子方法compareAndSwapLong看看是不是更進一步。

class CASCounter implements Counter { private volatile long counter = 0; private Unsafe unsafe; private long offset; public CASCounter() throws Exception { unsafe = getUnsafe(); offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter")); } @Override public void increment() { long before = counter; while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) { before = counter; } } @Override public long getCounter() { return counter; }}

輸出:

Counter result: 100000000Time?passed?in?ms:?6454

看起來和使用原子類是一樣的效果,難道原子類使用了Unsafe?答案是YES。

事實上該例子非常簡單但表現(xiàn)出了Unsafe的強大功能。

就像前面提到的 CAS原語可以用來實現(xiàn)高效的無鎖數(shù)據(jù)結(jié)構(gòu)。實現(xiàn)的原理很簡單:
1、擁有一個狀態(tài);
2、創(chuàng)建一個它的副本;
3、修改該副本;
4、執(zhí)行 CAS 操作;
5、如果失敗就重復(fù)執(zhí)行;

事實上,在真實的環(huán)境它的實現(xiàn)難度超過你的想象,這其中有需要類似ABA,指令重排序這樣的問題。

14、結(jié)論

盡管Unsafe有這么多有用的應(yīng)用,但是盡量不要使用。當然了使用JDK中利用了Unsafe實現(xiàn)的類是可以的。或者你對你代碼功力非常自信,可以自己挖坑再填坑哈~

歡迎小伙伴們留言交流~~

總結(jié)

以上是生活随笔為你收集整理的java 中random类使用_Java中的天使和魔鬼:Unsafe类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。