sun.misc.Unsafe类详解

最近在看Java多线程的知识,在看到actomic类的时候发现了一个很少用到的类:unsafe。其实之前好像也见过相关的文章,但是当时没有留意,现在又碰上了,所以看了一些文章,汇总放在这里,以后多来看看。

sun.misc.unSafe包的下载地址放在最前面吧: http://download.csdn.net/detail/dfdsggdgg/9535347


如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现。现在,就让我们更进一步看一下它把!

[html]  view plain  copy
  1. public final boolean compareAndSet(int expect, int update){  
  2.     returnunsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  3. }  

在这里,我们看到一个特殊的变量unsafe。它是sun.misc.Unsafe类型。从名字看,这个类应该是封装了一些不安全的操作。那什么操作是不安全的呢?学习过C或者C++的话,大家应该知道,指针是不安全的。这也是在Java中把指针去除的重要原因。如果指针指错了位置,或者计算指针偏移量时出错,结果可能是灾难性的,你很有可能会覆盖别人的内存,导致系统奔溃。

而这里的Unsafe就是封装了一些类似指针的操作。compareAndSwapInt()方法是一个navtive方法。它的几个参数含义如下:

[html]  view plain  copy
  1. public final native boolean compareAndSwapInt(Object o,long offset,int expected,int x);  

第一个参数o为给定的对象,offset为对象内的偏移量(其实就是一个字段到对象头部的偏移量,通过这个偏移量可以快速定位字段),expected表示期望值,x表示要设置的值。如果指定的字段的值等于expected,那么就会把它设置为x。

不难看出,compareAndSwapInt()方法的内部,必然是使用CAS原子指令来完成的。此外,Unsafe类还提供了一些方法,主要有以下几个(以Int操作为例,其他数据类型是类似的):

[java]  view plain  copy
  1. //获得给定对象偏移量上的int值  
  2. public native int getInt(Object o, long offset);  
  3. //设置给定对象偏移量上的int值  
  4. public native void putInt(Object o, long offset, int x);  
  5. //获得字段在对象中的偏移量  
  6. public native long objectFieldOffset(Field f);  
  7. //设置给定对象的int值,使用volatile语义  
  8. public native void putIntVolatile(Object o, long offset,int x);  
  9. //获得给定对象对象的int值,使用volatile语义  
  10. public native int    getIntVolatile(Object o, long offset);  
  11. //和putIntVolatile()一样,但是它要求被操作字段就是volatile类型的  
  12. public native void putOrderedInt(Object o, long offset, intx);  

如果大家还记得“3.3.4 深度剖析ConcurrentLinkedQueue”一节中的描述的ConcurrentLinkedQueue实现,应该对ConcurrentLinkedQueue中的Node还有些印象。Node一些CAS操作也都是使用Unsafe类来实现的。大家可以回顾一下,以加深对Unsafe类的印象。

这里就可以看到,虽然Java抛弃了指针。但是在关键时刻,类似指针的技术还是必不可少的。这里底层的Unsafe实现就是最好的例子。但是很不幸,JDK的开发人员并不希望大家使用这个类。获得Unsafe实例的方法是调动其工厂方法getUnsafe()。但是,它的实现却是这样:

[java]  view plain  copy
  1. public static Unsafe getUnsafe() {  
  2.     Class cc =Reflection.getCallerClass();  
  3.     if(cc.getClassLoader() != null)  
  4.         throw newSecurityException("Unsafe");  
  5.     return theUnsafe;  
  6. }  

注意加粗部分的代码,它会检查调用getUnsafe()函数的类,如果这个类的ClassLoader不为null,就直接抛出异常,拒绝工作。因此,这也使得我们自己的应用程序无法直接使用Unsafe类。它是一个JDK内部使用的专属类。

注意:根据Java 类加载器的工作原理,应用程序的类由AppLoader加载。而系统核心类,如rt.jar中的类由Bootstrap类加载器加载。Bootstrap加载器没有Java对象的对象,因此试图获得这个类加载器会返回null。所以,当一个类的类加载器为null时,说明它是由Bootstrap加载的,而这个类也极有可能是rt.jar中的类。


下面是一些这个类的特殊用法:真的很强大。

实例化sun.misc.Unsafe

    如果你尝试创建Unsafe类的实例,基于以下两种原因是不被允许的。

    1)、Unsafe类的构造函数是私有的;

    2)、虽然它有静态的getUnsafe()方法,但是如果你尝试调用Unsafe.getUnsafe(),会得到一个SecutiryException。这个类只有被JDK信任的类实例化。

    但是这总会是有变通的解决办法的,一个简单的方式就是使用反射进行实例化:

[java]  view plain  copy
  1. Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference  
  2. f.setAccessible(true);  
  3. Unsafe unsafe = (Unsafe) f.get(null);  

    注:IDE如Eclipse对会这样的使用报错,不过不用担心,直接运行代码就行,可以正常运行的。

    (译者注:还有一种解决方案,就是将Eclipse中这种限制获取由错误,修改为警告,具体操作为将Windows->Preference...->Java->Compiler->Errors/Warnings中的"Deprecated and restricted API",级别由Error修改为Warning就可以了)

    现在进入主题,使用这个对象我们可以做如下“有趣的”事情。


    使用sun.misc.Unsafe

    1)、突破限制创建实例

    通过allocateInstance()方法,你可以创建一个类的实例,但是却不需要调用它的构造函数、初使化代码、各种JVM安全检查以及其它的一些底层的东西。即使构造函数是私有,我们也可以通过这个方法创建它的实例。

    (这个对单例模式情有独钟的程序员来说将会是一个噩梦,它们没有办法阻止这种方式调用大笑

    看下面一个实例(注:为了配合这个主题,译者将原实例中的public构造函数修改为了私有的):

 

[java]  view plain  copy
  1. public class UnsafeDemo {  
  2.     public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {  
  3.         Field f = Unsafe.class.getDeclaredField("theUnsafe"); // Internal reference  
  4.         f.setAccessible(true);  
  5.         Unsafe unsafe = (Unsafe) f.get(null);  
  6.   
  7.         // This creates an instance of player class without any initialization  
  8.         Player p = (Player) unsafe.allocateInstance(Player.class);  
  9.         System.out.println(p.getAge()); // Print 0  
  10.   
  11.         p.setAge(45); // Let's now set age 45 to un-initialized object  
  12.         System.out.println(p.getAge()); // Print 45  
  13.     }  
  14. }  
  15.   
  16. class Player {  
  17.     private int age = 12;  
  18.   
  19.     private Player() {  
  20.         this.age = 50;  
  21.     }  
  22.   
  23.     public int getAge() {  
  24.         return this.age;  
  25.     }  
  26.   
  27.     public void setAge(int age) {  
  28.         this.age = age;  
  29.     }  
  30. }  


    2)、使用直接获取内存的方式实现浅克隆

    如何实现浅克隆?在clone(){...}方法中调用super.clone(),对吗?这里存在的问题是首先你必须继续Cloneable接口,并且在所有你需要做浅克隆的对象中实现clone()方法,对于一个懒懒的程序员来说,这个工作量太大了。

    我不推荐上面的做法而是直接使用Unsafe,我们可以仅使用几行代码就实现浅克隆,并且它可以像某些工具类一样用于任意类的克隆。

    这个戏法就是把一个对象的字节码拷贝到内存的另外一个地方,然后再将这个对象转换为被克隆的对象类型。

    

    3)、来自黑客的密码安全

    这个好似很有趣吧?实事就是这样的。开发人员创建密码或者是保证密码到字符串中,然后在应用程序的代码中使用这些密码,使用过后,聪明的程序员会把字符串的引用设为NULL,因此它就不会被引用着并且很容易被垃圾收集器给回收掉。

    但是从你将引用设为NULL到被垃圾收集器收集的这个时间段之内(原文:But from the time, you made the reference null to the time garbage collector kicks in),它是处于字符串池中的,并且在你系统中进行一个复杂的攻击(原文:And a sophisticated attack on your system),也是可以读取到你的内存区域并且获得密码,虽然机会很小,但是总是存在的。

    这就是为什么建议使用char[]数组存放密码,当使用完过后,你可以迭代处理当前数组,修改/清空这些字符。

    另外一个方式就是使用魔术类Unsafe。你可以创建另外一个和当前密码字符串具有相同长度的临时字符串,将临时密码中的每个字符都设值为"?"或者"*"(任何字符都可以),当你完成密码的逻辑后,你只需要简单的将临时密码中的字节数组拷贝到原始的密码串中,这就是使用临时密码覆盖真实的密码。

    示例代码可能会是这样:

[java]  view plain  copy
  1. String password = new String("l00k@myHor$e");  
  2. String fake = new String(password.replaceAll(".""?"));  
  3. System.out.println(password); // l00k@myHor$e  
  4. System.out.println(fake); // ????????????  
  5.    
  6. getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));  
  7.    
  8. System.out.println(password); // ????????????  
  9. System.out.println(fake); // ????????????  

    运行时动态创建类

    我们可以在运行时运态的创建类,例如通过编译后的.class文件,操作方式就是将.class文件读取到字节数据组中,并将其传到defineClass方法中。

[java]  view plain  copy
  1. //Sample code to craeet classes  
  2. byte[] classContents = getClassContent();  
  3. Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);  
  4. c.getMethod("a").invoke(c.newInstance(), null);  
  5.    
  6. //Method to read .class file  
  7. private static byte[] getClassContent() throws Exception {  
  8.     File f = new File("/home/mishadoff/tmp/A.class");  
  9.     FileInputStream input = new FileInputStream(f);  
  10.     byte[] content = new byte[(int)f.length()];  
  11.     input.read(content);  
  12.     input.close();  
  13.     return content;  
  14. }  
    

    4)、超大数组

    从所周知,常量Integer.MAX_VALUE是JAVA中数组长度的最大值,如果你想创建一个非常大的数组(虽然在通常的应用中不可能会用上),可以通过对内存进行直接分配实现。

    下面这个示例将会创建分配一段连续的内存(数组),它的容易是允许最大容量的两倍。

[java]  view plain  copy
  1. class SuperArray {  
  2.     private final static int BYTE = 1;  
  3.     private long size;  
  4.     private long address;  
  5.        
  6.     public SuperArray(long size) {  
  7.         this.size = size;  
  8.         //得到分配内存的起始地址  
  9.         address = getUnsafe().allocateMemory(size * BYTE);  
  10.     }  
  11.     public void set(long i, byte value) {  
  12.         getUnsafe().putByte(address + i * BYTE, value);  
  13.     }  
  14.     public int get(long idx) {  
  15.         return getUnsafe().getByte(address + idx * BYTE);  
  16.     }  
  17.     public long size() {  
  18.         return size;  
  19.     }  
  20. }  
    应用示例

[java]  view plain  copy
  1. long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;  
  2. SuperArray array = new SuperArray(SUPER_SIZE);  
  3. System.out.println("Array size:" + array.size()); // 4294967294  
  4. for (int i = 0; i < 100; i++) {  
  5.     array.set((long)Integer.MAX_VALUE + i, (byte)3);  
  6.     sum += array.get((long)Integer.MAX_VALUE + i);  
  7. }  
  8. System.out.println("Sum of 100 elements:" + sum);  // 300  

    但请注意这可能会导致JVM挂掉。

    sun.misc.Unsafe提供了可以随意查看及修改JVM中运行时的数据结构,尽管这些功能在JAVA开发本身是不适用的,Unsafe是一个用于研究学习HotSpot虚拟机非常棒的工具,因为它不需要调用C++代码,或者需要创建即时分析的工具。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: sun.misc.unsafe.park(native me) 是Java中的一个方法,它是用来阻塞当前线程的。具体来说,它会使当前线程进入等待状态,直到被唤醒或者被中断。这个方法通常用于实现线程的同步和互斥。 ### 回答2: sun.misc.unsafe.park(native me 是一个Java API中的方法,它通过使用Unsafe的park方法来使当前线程进入休眠状态。在这个方法中,native关键字表示它是由本地代码实现的,也就是说具体的实现是由底层的操作系统提供的。 调用sun.misc.unsafe.park(native me 方法可以实现线程的等待和唤醒操作。当当前线程执行到这个方法时,会立即进入休眠状态,暂停自己的执行,直到其他线程通过调用Unsafe的unpark方法唤醒它。 这个机制通常用于线程同步的场景,可以实现线程之间的协作。比如,一个生产者线程通过调用Unsafe的park方法进入休眠状态,等待某个条件满足后再继续执行;而一个消费者线程在满足某个条件后,通过调用Unsafe的unpark方法唤醒生产者线程,使其恢复执行。 需要注意的是,sun.misc.unsafe.park(native me 方法通常不建议直接使用,因为它是一个内部API,可能会在未来的版本中被移除或者修改。在实际应用中,可以使用更高级的并发工具,如Lock和Condition、CountDownLatch、Semaphore等来实现线程的等待和唤醒操作,这些API提供了更加安全和可靠的线程同步机制。 ### 回答3: sun.misc.unsafe.park(native me)是Java中的一个方法,它是由sun.misc.Unsafe提供的。该方法主要用于线程的阻塞等待。 在Java中,线程可以通过调用park方法进入阻塞状态,直到某个条件满足或者其他线程唤醒它。park方法是一种低级的阻塞机制,它不会占用CPU资源,因此适用于一些需要较长等待时间的场景。 使用park方法时,我们需要传入一个native me参数。native me是指要阻塞的线程对象,也就是当前执行park方法的线程自身。 park方法可以通过其他线程的unpark方法来唤醒被阻塞的线程。unpark方法会给指定的线程一个许可证,使得park方法立即返回。 使用park方法进行线程的阻塞等待可以提高线程的效率和性能,避免了一直占用CPU资源。 需要注意的是,sun.misc.unsafe.park(native me)方法属于sun.misc包下的不稳定的、不建议直接使用的API。在实际开发中,应该尽量避免使用这些API,而是使用Java提供的高级并发库,如java.util.concurrent包下的Locks和Conditions,或者使用更高层次的并发框架,如线程池。这些库和框架提供了更稳定、易用且可扩展的线程同步和阻塞等待机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值