前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java Unsafe 类

Java Unsafe 类

作者头像
干货满满张哈希
发布2021-04-12 16:20:31
5290
发布2021-04-12 16:20:31
举报

Unsafe类是啥?

Java最初被设计为一种安全的受控环境。尽管如此,Java HotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。但是丝毫不建议在生产环境中使用这个后门。因为这个API十分不安全、不轻便、而且不稳定。这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改。有时它可以被用来在不适用C++调试的情况下学习虚拟机内部结构,有时也可以被拿来做性能监控和开发工具。

为什么叫Unsafe?

Java官方不推荐使用Unsafe类,因为官方认为,这个类别人很难正确使用,非正确使用会给JVM带来致命错误。而且未来Java可能封闭丢弃这个类。

如何使用Unsafe?

1. 获取Unsafe实例:

通读Unsafe源码,Unsafe提供了一个私有的静态实例,并且通过检查classloader是否为null来避免java程序直接使用unsafe:

//Unsafe源码
private static final Unsafe theUnsafe;
@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if(var0.getClassLoader() != null) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

我们可以通过如下代码反射获取Unsafe静态类:

/**
 * 获取Unsafe
 */
Field f = null;
Unsafe unsafe = null;
try {
    f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

2. 通过Unsafe分配使用堆外内存:

C++中有malloc,realloc和free方法来操作内存。在Unsafe类中对应为:

//分配var1字节大小的内存,返回起始地址偏移量
public native long allocateMemory(long var1);
//重新给var1起始地址的内存分配长度为var3字节大小的内存,返回新的内存起始地址偏移量
public native long reallocateMemory(long var1, long var3);
//释放起始地址为var1的内存
public native void freeMemory(long var1);

分配内存方法还有重分配内存方法都是分配的堆外内存,返回的是一个long类型的地址偏移量。这个偏移量在你的Java程序中每块内存都是唯一的。 举例:

/**
 * 在堆外分配一个byte
 */
long allocatedAddress = unsafe.allocateMemory(1L);
unsafe.putByte(allocatedAddress, (byte) 100);
byte shortValue = unsafe.getByte(allocatedAddress);
System.out.println(new StringBuilder().append("Address:").append(allocatedAddress).append(" Value:").append(shortValue));
/**
 * 重新分配一个long
 */
allocatedAddress = unsafe.reallocateMemory(allocatedAddress, 8L);
unsafe.putLong(allocatedAddress, 1024L);
long longValue = unsafe.getLong(allocatedAddress);
System.out.println(new StringBuilder().append("Address:").append(allocatedAddress).append(" Value:").append(longValue));
/**
 * Free掉,这个数据可能脏掉
 */
unsafe.freeMemory(allocatedAddress);
longValue = unsafe.getLong(allocatedAddress);
System.out.println(new StringBuilder().append("Address:").append(allocatedAddress).append(" Value:").append(longValue));

输出:

Address:46490464 Value:100
Address:46490480 Value:1024
Address:46490480 Value:22

3. 操作类对象

我们可以通过Unsafe类来操作修改某一field。原理是首先获取对象的基址(对象在内存的偏移量起始地址)。之后获取某个filed在这个对象对应的类中的偏移地址,两者相加修改。

 /**
 * 获取类的某个对象的某个field偏移地址
 */
try {
    f = SampleClass.class.getDeclaredField("i");
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}
long iFiledAddressShift = unsafe.objectFieldOffset(f);
SampleClass sampleClass = new SampleClass();
//获取对象的偏移地址,需要将目标对象设为辅助数组的第一个元素(也是唯一的元素)。由于这是一个复杂类型元素(不是基本数据类型),它的地址存储在数组的第一个元素。然后,获取辅助数组的基本偏移量。数组的基本偏移量是指数组对象的起始地址与数组第一个元素之间的偏移量。
Object helperArray[]    = new Object[1];
helperArray[0]      = sampleClass;
long baseOffset     = unsafe.arrayBaseOffset(Object[].class);
long addressOfSampleClass    = unsafe.getLong(helperArray, baseOffset);
int i = unsafe.getInt(addressOfSampleClass + iFiledAddressShift);
System.out.println(new StringBuilder().append(" Field I Address:").append(addressOfSampleClass).append("+").append(iFiledAddressShift).append(" Value:").append(i));

输出:

Field I Address:3610777760+24 Value:5

4. 线程挂起和恢复

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

public class LockSupport {  
    public static void unpark(Thread thread) {  
        if (thread != null)  
            unsafe.unpark(thread);  
    }  

    public static void park(Object blocker) {  
        Thread t = Thread.currentThread();  
        setBlocker(t, blocker);  
        unsafe.park(false, 0L);  
        setBlocker(t, null);  
    }  

    public static void parkNanos(Object blocker, long nanos) {  
        if (nanos > 0) {  
            Thread t = Thread.currentThread();  
            setBlocker(t, blocker);  
            unsafe.park(false, nanos);  
            setBlocker(t, null);  
        }  
    }  

    public static void parkUntil(Object blocker, long deadline) {  
        Thread t = Thread.currentThread();  
        setBlocker(t, blocker);  
        unsafe.park(true, deadline);  
        setBlocker(t, null);  
    }  

    public static void park() {  
        unsafe.park(false, 0L);  
    }  

    public static void parkNanos(long nanos) {  
        if (nanos > 0)  
            unsafe.park(false, nanos);  
    }  

    public static void parkUntil(long deadline) {  
        unsafe.park(true, deadline);  
    }  
}  

5. CAS操作

/** 
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。 
*  
* @param obj 需要更新的对象 
* @param offset obj中整型field的偏移量 
* @param expect 希望field中存在的值 
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值 
* @return 如果field的值被更改返回true 
*/  
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);  

6. Clone

如何实现浅克隆?在clone(){…}方法中调用super.clone(),对吗?这里存在的问题是首先你必须继续Cloneable接口,并且在所有你需要做浅克隆的对象中实现clone()方法,对于一个懒懒的程序员来说,这个工作量太大了。 我不推荐上面的做法而是直接使用Unsafe,我们可以仅使用几行代码就实现浅克隆,并且它可以像某些工具类一样用于任意类的克隆。 首先,我们需要一个计算Object大小的工具类:

class ObjectInfo {
    /**
     * Field name
     */
    public final String name;
    /**
     * Field type name
     */
    public final String type;
    /**
     * Field data formatted as string
     */
    public final String contents;
    /**
     * Field offset from the start of parent object
     */
    public final int offset;
    /**
     * Memory occupied by this field
     */
    public final int length;
    /**
     * Offset of the first cell in the array
     */
    public final int arrayBase;
    /**
     * Size of a cell in the array
     */
    public final int arrayElementSize;
    /**
     * Memory occupied by underlying array (shallow), if this is array type
     */
    public final int arraySize;
    /**
     * This object fields
     */
    public final List children;

    public ObjectInfo(String name, String type, String contents, int offset, int length, int arraySize,
                      int arrayBase, int arrayElementSize) {
        this.name = name;
        this.type = type;
        this.contents = contents;
        this.offset = offset;
        this.length = length;
        this.arraySize = arraySize;
        this.arrayBase = arrayBase;
        this.arrayElementSize = arrayElementSize;
        children = new ArrayList(1);
    }

    public void addChild(final ObjectInfo info) {
        if (info != null)
            children.add(info);
    }

    /**
     * Get the full amount of memory occupied by a given object. This value may be slightly less than
     * an actual value because we don't worry about memory alignment - possible padding after the last object field.
     * 

我们通过这两个类计算一个Object的大小,通过Unsafe的 public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7)方法来拷贝: 两个工具方法:

private static Object helperArray[] = new Object[1];
/**
 * 获取对象起始位置偏移量
 * @param unsafe
 * @param object
 * @return
 */
public static long getObjectAddress(Unsafe unsafe, Object object){
    helperArray[0] = object;
    long baseOffset = unsafe.arrayBaseOffset(Object[].class);
    return unsafe.getLong(helperArray, baseOffset);
}

private final static ClassIntrospector ci = new ClassIntrospector();

/**
 * 获取Object的大小
 * @param object
 * @return
 */
public static long getObjectSize(Object object){
    ObjectInfo res = null;
    try {
        res = ci.introspect(object);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return res.getDeepSize();
}

测试:

SampleClass sampleClass = new SampleClass();
sampleClass.setI(999);
sampleClass.setL(999999999L);

SampleClass sampleClassCopy = new SampleClass();
long copyAddress = getObjectAddress(unsafe,sampleClassCopy);
unsafe.copyMemory(sampleClass, 0, null,copyAddress, getObjectSize(sampleClass));
i = unsafe.getInt(copyAddress + iFiledAddressShift);
System.out.println(i);
System.out.println(sampleClassCopy.getL());

输出:

999
999999999
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-07-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Unsafe类是啥?
  • 为什么叫Unsafe?
  • 如何使用Unsafe?
    • 1. 获取Unsafe实例:
      • 2. 通过Unsafe分配使用堆外内存:
        • 3. 操作类对象
          • 4. 线程挂起和恢复
            • 5. CAS操作
              • 6. Clone
              相关产品与服务
              应用性能监控
              应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档