Unsafe魔法类有什么作用?如何使用?

2020-05-05 14:34:53 java常见问答 4920

对java开发相关知识感兴趣的小伙伴们,还记得Unsafe魔法类吗?以大家对Unsafe的了解,认为Unsafe魔法类应该有什么作用呢?它应该如何去使用呢?有兴趣的朋友可以跟小编一起来看看吧。

首先Unsafe类为我们提供了访问底层的机制,这种机制呢,是仅仅提供了java核心类库的使用,而不应该去被普通用户使用的。

不过呢,为了更好地去了解java的生态体系,我们也应该去学习它了解它,不说要深入到底层的C/C++代码,但需要能了解它的基本功能。

那么获取Unsafe的实例如下:

查看Unsafe的源码我们会发现它提供了一个叫getUnsafe()的静态方法。

@CallerSensitivepublicstaticUnsafegetUnsafe()
{
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader()))
    {
        thrownewSecurityException("Unsafe");
    }
    else
    {
        returntheUnsafe;
    }
}

但是呢,如果直接调用这个方法就会抛出一个SecurityException的异常,这是因为Unsafe仅仅供java内部类使用,外部类都不应该使用它。

那我们就没有方法了吗?其实当然不是啦,我们有反射啊,查看源码,我们会发现它有一个属性叫theUnsafe,所以我们直接通过反射拿到它就好了。

publicclassUnsafeTest
{
    publicstaticvoidmain(String[] args) throws NoSuchFieldException, IllegalAccessException
    {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafeunsafe = (Unsafe) f.get(null);
    }
}

我们使用Unsafe实例化一个类。假如说我们有一个简单的类如下:

classUser
{
    intage;
    publicUser()
    {
        this.age = 10;
    }
}

要是我们通过构造方法实例化这个类,age属性就会返回10。

User user1 = newUser(); // 打印10
System.out.println(user1.age);

要是我们调用Unsafe来实例化呢?

User user2 = (User)unsafe.allocateInstance(User.class); 
// 打印0
System.out.println(user2.age);

age将返回0,因为Unsafe.allocateInstance()方法只会给对象分配内存,并且不会调用构造方法,所以这里只会返回int类型的默认值0。

可以修改私有字段的值。使用Unsafe的putXXX()方法,我们其实可以修改任意私有字段的值。

publicclassUnsafeTest
{
    publicstaticvoidmain(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException
    {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafeunsafe = (Unsafe) f.get(null);
        User user = newUser();
        Field age = user.getClass()
            .getDeclaredField("age");
        unsafe.putInt(user, unsafe.objectFieldOffset(age), 20);
        // 打印20System.out.println(user.getAge()); 
    }
}
classUser
{
    privateintage;
    publicUser()
    {
        this.age = 10;
    }
    publicintgetAge()
    {
        returnage;
    }
}

一旦呢我们通过了反射调用得到字段age,我们就可以使用Unsafe将其值更改为任何其他int值。

抛出了checked异常。我们知道如果代码抛出了checked异常,要不就使用try...catch去捕获它了,要不就在方法签名上定义这个异常,但是,通过Unsafe其实可以抛出一个checked异常,同时却不用捕获或在方法签名上去定义它。

// 使用正常方式抛出IOException需要定义在方法签名上往外抛
publicstaticvoidreadFile() throwsIOException
{
    thrownewIOException();
}
// 使用Unsafe抛出异常不需要定义在方法签名上往外抛
publicstaticvoidreadFileUnsafe()
{
    unsafe.throwException(newIOException());
}

使用堆外内存。如果说进程在运行过程中JVM上的内存不足了,这就会导致频繁的进行GC。那么理想的情况下,我们可以考虑使用堆外内存,这是一块不受JVM管理的内存。

在使用Unsafe的allocateMemory()方法时,我们可以直接在堆外分配内存,这可能非常的有用,但是我们得要记住,这个内存是不受JVM管理的,所以我们要调用freeMemory()方法手动去释放它。

假如说我们要在堆外创建一个较大的int数组,我们可以使用allocateMemory()方法去实现:

classOffHeapArray
{ // 一个int等于4个字节
    private static final int INT = 4;
    privatelongsize;
    privatelongaddress;
    privatestaticUnsafeunsafe;
    static
    {
        try
        {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
        }
        catch (NoSuchFieldException e)
        {
            e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
    } // 构造方法,分配内存
    publicOffHeapArray(longsize)
    {
        this.size = size;
        // 参数字节数address =unsafe.allocateMemory(size * INT);
    }
    // 获取指定索引处的元素publicintget(longi){returnunsafe.getInt(address + i * INT);
}
// 设置指定索引处的元素publicvoidset(longi,intvalue){unsafe.putInt(address + i * INT,value);
}
// 元素个数publiclongsize(){
returnsize;
} // 释放堆外内存publicvoidfreeMemory(){unsafe.freeMemory(address);
}
}
//在构造方法中调用allocateMemory() 分配内存, 在使用完成后调用freeMemory() 释放内存。

使用方式如下所示:

OffHeapArray offHeapArray = newOffHeapArray(4);
offHeapArray.set(0, 1);
offHeapArray.set(1, 2);
offHeapArray.set(2, 3);
offHeapArray.set(3, 4);
offHeapArray.set(2, 5);
// 在索引2的位置重复放入元素
int sum = 0;
for (inti = 0; i < offHeapArray.size(); i++)
{
    sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);
offHeapArray.freeMemory();

最后,一定要记得调用freeMemory()将内存释放回操作系统。

好了,以上就是有关Unsafe魔法类的所有内容了,还想了解更多java架构师的相关信息吗?如果答案是肯定的,那么就赶快来关注本站消息吧。