一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java中的魔法類:sun.misc.Unsafe示例詳解

Java中的魔法類:sun.misc.Unsafe示例詳解

2021-05-04 14:05素軒 Java教程

Java是一個安全的開發工具,它阻止開發人員犯很多低級的錯誤,而大部份的錯誤都是基于內存管理方面的。如果你想搞破壞,可以使用Unsafe這個類。下面這篇文章主要給大家介紹了關于Java中魔法類:sun.misc.Unsafe的相關資料,需要的

前言

unsafe類在jdk 源碼的多個類中用到,這個類的提供了一些繞開jvm的更底層功能,基于它的實現可以提高效率。但是,它是一把雙刃劍:正如它的名字所預示的那樣,它是unsafe的,它所分配的內存需要手動free(不被gc回收)。unsafe類,提供了jni某些功能的簡單替代:確保高效性的同時,使事情變得更簡單。

這個類是屬于sun.* api中的類,并且它不是j2se中真正的一部份,因此你可能找不到任何的官方文檔,更可悲的是,它也沒有比較好的代碼文檔。

這篇文章主要是以下文章的整理、翻譯。

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

1. unsafe api的大部分方法都是native實現,它由105個方法組成,主要包括以下幾類:

(1)info相關。主要返回某些低級別的內存信息:addresssize(), pagesize()

(2)objects相關。主要提供object和它的域操縱方法:allocateinstance(),objectfieldoffset()

(3)class相關。主要提供class和它的靜態域操縱方法:staticfieldoffset(),defineclass(),defineanonymousclass(),ensureclassinitialized()

(4)arrays相關。數組操縱方法:arraybaseoffset(),arrayindexscale()

(5)synchronization相關。主要提供低級別同步原語(如基于cpu的cas(compare-and-swap)原語):monitorenter(),trymonitorenter(),monitorexit(),compareandswapint(),putorderedint()

(6)memory相關。直接內存訪問方法(繞過jvm堆直接操縱本地內存):allocatememory(),copymemory(),freememory(),getaddress(),getint(),putint()

2. unsafe類實例的獲取

unsafe類設計只提供給jvm信任的啟動類加載器所使用,是一個典型的單例模式類。它的實例獲取方法如下:

?
1
2
3
4
5
6
public static unsafe getunsafe() {
 class cc = sun.reflect.reflection.getcallerclass(2);
 if (cc.getclassloader() != null)
  throw new securityexception("unsafe");
 return theunsafe;
}

非啟動類加載器直接調用unsafe.getunsafe()方法會拋出securityexception(具體原因涉及jvm類的雙親加載機制)。

解決辦法有兩個,其一是通過jvm參數-xbootclasspath指定要使用的類為啟動類,另外一個辦法就是java反射了。

?
1
2
3
field f = unsafe.class.getdeclaredfield("theunsafe");
f.setaccessible(true);
unsafe unsafe = (unsafe) f.get(null);

通過將private單例實例暴力設置accessible為true,然后通過field的get方法,直接獲取一個object強制轉換為unsafe。在ide中,這些方法會被標志為error,可以通過以下設置解決:

?
1
2
preferences -> java -> compiler -> errors/warnings ->
deprecated and restricted api -> forbidden reference -> warning

3. unsafe類“有趣”的應用場景

(1)繞過類初始化方法。當你想要繞過對象構造方法、安全檢查器或者沒有public的構造方法時,allocateinstance()方法變得非常有用。

?
1
2
3
4
5
6
7
class a {
 private long a; // not initialized value
 public a() {
  this.a = 1; // initialization
 }
 public long a() { return this.a; }
}

以下是構造方法、反射方法和allocateinstance()的對照

?
1
2
3
4
5
6
7
8
a o1 = new a(); // constructor
o1.a(); // prints 1
 
a o2 = a.class.newinstance(); // reflection
o2.a(); // prints 1
 
a o3 = (a) unsafe.allocateinstance(a.class); // unsafe
o3.a(); // prints 0

allocateinstance()根本沒有進入構造方法,在單例模式時,我們似乎看到了危機。

(2)內存修改

內存修改在c語言中是比較常見的,在java中,可以用它繞過安全檢查器。

考慮以下簡單準入檢查規則:

?
1
2
3
4
5
6
7
class guard {
 private int access_allowed = 1;
 
 public boolean giveaccess() {
  return 42 == access_allowed;
 }
}

在正常情況下,giveaccess總會返回false,但事情不總是這樣

?
1
2
3
4
5
6
7
8
9
guard guard = new guard();
guard.giveaccess(); // false, no access
 
// bypass
unsafe unsafe = getunsafe();
field f = guard.getclass().getdeclaredfield("access_allowed");
unsafe.putint(guard, unsafe.objectfieldoffset(f), 42); // memory corruption
 
guard.giveaccess(); // true, access granted

通過計算內存偏移,并使用putint()方法,類的access_allowed被修改。在已知類結構的時候,數據的偏移總是可以計算出來(與c++中的類中數據的偏移計算是一致的)。

(3)實現類似c語言的sizeof()函數

通過結合java反射和objectfieldoffset()函數實現一個c-like sizeof()函數。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
}

算法的思路非常清晰:從底層子類開始,依次取出它自己和它的所有超類的非靜態域,放置到一個hashset中(重復的只計算一次,java是單繼承),然后使用objectfieldoffset()獲得一個最大偏移,最后還考慮了對齊。

在32位的jvm中,可以通過讀取class文件偏移為12的long來獲取size。

?
1
2
3
4
public static long sizeof(object object){
 return getunsafe().getaddress(
  normalize(getunsafe().getint(object, 4l)) + 12l);
}

其中normalize()函數是一個將有符號int轉為無符號long的方法

?
1
2
3
4
private static long normalize(int value) {
 if(value >= 0) return value;
 return (0l >>> 32) & value;
}

兩個sizeof()計算的類的尺寸是一致的。最標準的sizeof()實現是使用java.lang.instrument,但是,它需要指定命令行參數-javaagent。

(4)實現java淺復制

標準的淺復制方案是實現cloneable接口或者自己實現的復制函數,它們都不是多用途的函數。通過結合sizeof()方法,可以實現淺復制。

?
1
2
3
4
5
6
7
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()分別將對象轉換到它的地址以及相反操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
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];
}

以上的淺復制函數可以應用于任意java對象,它的尺寸是動態計算的。

(5)消去內存中的密碼

密碼字段存儲在string中,但是,string的回收是受到jvm管理的。最安全的做法是,在密碼字段使用完之后,將它的值覆蓋。

?
1
2
3
4
5
6
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] = '?';
}

(6)動態加載類

標準的動態加載類的方法是class.forname()(在編寫jdbc程序時,記憶深刻),使用unsafe也可以動態加載java 的class文件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
byte[] classcontents = getclasscontent();
class c = getunsafe().defineclass(
    null, classcontents, 0, classcontents.length);
 c.getmethod("a").invoke(c.newinstance(), null); // 1
getclasscontent()方法,將一個class文件,讀取到一個byte數組。
 
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;
}

動態加載、代理、切片等功能中可以應用。

(7)包裝受檢異常為運行時異常。

?
1
getunsafe().throwexception(new ioexception());

當你不希望捕獲受檢異常時,可以這樣做(并不推薦)。

(8)快速序列化

標準的java serializable速度很慢,它還限制類必須有public無參構造函數。externalizable好些,它需要為要序列化的類指定模式。流行的高效序列化庫,比如kryo依賴于第三方庫,會增加內存的消耗。可以通過getint(),getlong(),getobject()等方法獲取類中的域的實際值,將類名稱等信息一起持久化到文件。kryo有使用unsafe的嘗試,但是沒有具體的性能提升的數據。(http://code.google.com/p/kryo/issues/detail?id=75)

(9)在非java堆中分配內存

使用java 的new會在堆中為對象分配內存,并且對象的生命周期內,會被jvm gc管理。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
 }
}

unsafe分配的內存,不受integer.max_value的限制,并且分配在非堆內存,使用它時,需要非常謹慎:忘記手動回收時,會產生內存泄露;非法的地址訪問時,會導致jvm崩潰。在需要分配大的連續區域、實時編程(不能容忍jvm延遲)時,可以使用它。java.nio使用這一技術。

(10)java并發中的應用

通過使用unsafe.compareandswap()可以用來實現高效的無鎖數據結構。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
 }
}

通過測試,以上數據結構與java的原子變量的效率基本一致,java原子變量也使用unsafe的compareandswap()方法,而這個方法最終會對應到cpu的對應原語,因此,它的效率非常高。這里有一個實現無鎖hashmap的方案(http://www.azulsystems.com/about_us/presentations/lock-free-hash ,這個方案的思路是:分析各個狀態,創建拷貝,修改拷貝,使用cas原語,自旋鎖),在普通的服務器機器(核心<32),使用concurrenthashmap(jdk8以前,默認16路分離鎖實現,jdk8中concurrenthashmap已經使用無鎖實現)明顯已經夠用。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://www.cnblogs.com/suxuan/p/4948608.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 视频高h| 国产精品1024永久免费视频 | 91久久99热青草国产 | 精品人伦一区二区三区潘金莲 | 全程粗语对白视频videos | 9自拍视频在线观看 | 国产精品免费小视频 | 国产91短视频 | 日噜噜| 2022最新国产在线 | 久久精麻豆亚洲AV国产品 | 荡女人人爱全文免费阅读 | 毛片免费观看 | jzzjlzz亚洲乱熟在线播放 | 久久视频在线视频观看天天看视频 | 污樱桃视频| avtt在线| 国产一级免费片 | 免费看h片的网站 | 成人免费国产欧美日韩你懂的 | 四虎影视在线影院在线观看观看 | 免费久久久久 | 久久亚洲精品AV无码四区 | 99九九国产精品免费视频 | jk制服蕾丝超短裙流白浆 | 欧美一级裸片又黄又裸 | 91久操 | 超强台风免费观看完整版视频 | 99精品久久精品一区二区小说 | 成人福利网站 | 爽好舒服快想要免费看 | 美女秘密网站 | 亚洲mm色国产网站 | 亚洲欧美精品一区天堂久久 | 女子监狱第二季在线观看免费完整版 | 欧美一区二区三区精品 | 91精品久久 | 99热这里只精品99re66 | 高清不卡免费一区二区三区 | 日韩大片在线 | aaa一级最新毛片 |