Java中的Unsafe类详解

在这里插入图片描述

1. Unsafe 概念

Java 作为一门广泛应用于企业级应用开发的编程语言,为了保障程序的稳定性和安全性,通常限制了开发者对底层内存和硬件的直接访问。然而,Java 中的 Unsafe 类却为开发者提供了一种突破这些限制的方式,让他们可以直接操作内存、线程和对象,同时也引发了一系列潜在的风险和挑战。

2. Unsafe 构造及获取

Unsafe 类使用 final 修饰,不允许继承,且构造函数是 private,使用了饿汉式单例,通过一个静态方法 getUnsafe() 来获取实例。
先看一下源码:

public final class Unsafe {
    private static final Unsafe theUnsafe;

    private static native void registerNatives();

    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
}

在 getUnsafe 方法中对单例模式中的对象获取做了限制,如果是普通的调用会抛出一个 SecurityException 异常。只有由主类加载器加载的类才能调用这个方法。

获取 Unsafe 对象的方式

  1. 通过 Unsafe.getUnsafe()
    Unsafe unsafe = Unsafe.getUnsafe();
    
  2. 通过反射来获取
    Class<Unsafe> unsafeClass = Unsafe.class;
    Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    Object o = theUnsafe.get(null);
    Unsafe unsafe1 = (Unsafe) o;
    

3. 功能和应用

Unsafe 类提供了一些能够绕过 Java 语言安全机制的方法,例如直接操作内存、CAS(比较并交换)操作、分配和释放内存等。这使得开发者可以在某些情况下获得更高的性能,但同时也需要承担更大的风险和责任。

在这里插入图片描述

一些用途包括:

  • 手动管理内存:开发者可以使用 Unsafe 类手动分配和释放内存,从而实现更精细的内存管理。
  • 原子操作:Unsafe 提供了原子操作方法,使开发者可以实现高效的多线程并发控制。
  • 绕过安全检查:Unsafe 可以绕过一些 Java 语言层面的安全检查,但这也会导致潜在的安全漏洞。

3.1 内存管理

Unsafe 的内存管理功能主要包括:普通读写、volatile读写、有序读写、直接操作内存等分配内存与释放内存的功能。

3.1.1 普通读写

Unsafe 可以读写一个类的属性,即便这个属性是私有的,也可以对这个属性进行读写。

public native int getInt(Object var1, long var2);

public native void putInt(Object var1, long var2, int var4);

getInt 等于从对象的指定偏移地址处读取一个值。putInt等于在对象指定偏移处写入一个值。其他原始类型也提供有对应的方法。此外,Unsafe 的 getByte、putByte 方法提供了直接在一个地址上就行读写的功能。

3.1.2 volatile 读写

普通的读写无法保证可见性和有序性,而 volatile 读写就可以保证;但是相对普通读写更加昂贵。

public native int getIntVolatile(Object var1, long var2);

public native void putIntVolatile(Object var1, long var2, int var4);

3.1.3 有序读写

有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。
而与 volatile 写入相比 putOrderedXX 吸入代价相对较低,putOrderedXX 写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。

3.1.4 直接操作内存

Unsafe 提供了直接操作内存的能力:

public native long allocateMemory(long var1);

public native long reallocateMemory(long var1, long var3);

public native void setMemory(Object var1, long var2, long var4, byte var6);

public void setMemory(long var1, long var3, byte var5) {
    this.setMemory((Object)null, var1, var3, var5);
}

public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);

public native void freeMemory(long var1);

也提供了一些获取内存信息的方法:getAddress、addressSize、pageSize
注意:利用 copyMemory 方法可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现 clone 方法,但只能做到对象浅拷贝。

3.2 CAS

Unsafe 类的 CAS 操作作为 Java 的锁机制提供了一种新的解决方法,比如 AtomicInteger 等类都是通过该方法来实现的。compareAndSwap* 方法是原子的,可以避免的繁琐的锁机制,提高代码效率。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

CAS 一般用于乐观锁,它在 Java 中有广泛的应用,ReentrantLock、ConcurrentHashMap、ConcurrentLinkedQueue 等都有用到CAS来实现乐观锁。

3.3 偏移量

Unsafe 提供以下方法获取对象的指针,通过对指针进行偏移,不仅可以直接修改指针指向的数据(及时它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

// 获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量
public native long staticFieldOffset(Field var1);

// 获取费静态属性Field在对象实例中的偏移量,读写对象的费静态属性时会用到这个偏移量
public native long objectFieldOffset(Field var1);

// 返回Field所在的对象
public native Object staticFieldBase(Field var1);

// 返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量
public native int arrayBaseOffset(Class<?> var1);

// 计算数组中第一个元素所占用的内存空间
public native int arrayIndexScale(Class<?> var1);

3.4 线程调度

// 唤醒线程    
public native void unpark(Object var1);

// 挂起线程
public native void park(boolean var1, long var2);

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

3.5 类加载

// 方法定义一个类,用于动态地创建类
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

// 动态的创建一个匿名内部类
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

// 判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class<?> var1);

// 保证已经初始化过一个类
public native void ensureClassInitialized(Class<?> var1);

3.6 内存屏障

// 保证在这个屏障之前的所有读操作都已完成
public native void loadFence();

// 保证在这个屏障之前的所有写操作都已完成
public native void storeFence();

// 保证在这个屏障之前的所有读写操作都已完成
public native void fullFence();

3.7 其他操作

当然,Unsafe 类中还提供了大量其他的方法,比如上面提供的CAS操作,以 AtomicInteger 为例,当我们调用 getAndIncrement、getAndDecrement 等方法时,本质上调用就是 Unsafe 类的 getAndAddInt 方法。

// 返回系统指针的大小。返回值为4(32位系统)或8(64位系统)。
public native int addressSize();

// 内存页的大小,此值为2的幂次方
public native int pageSize();

4. 潜在风险和挑战

尽管 Unsafe 类在某些情况下可能提供了极大的灵活性和性能优势,但它也带来了一些严重的潜在问题:

  • 内存泄漏:手动管理内存可能导致难以察觉的内存泄漏,从而降低应用的稳定性和性能。
  • 安全漏洞:绕过安全检查可能导致潜在的安全漏洞,使应用容易受到恶意攻击。
  • 不稳定性:直接操作内存和线程可能导致应用的不稳定性和不可预测的行为。

5. 最佳实践

虽然 Unsafe 类提供了一些强大的功能,但在大多数情况下,开发者应该避免直接使用它。如果确实需要使用 Unsafe,请遵循以下最佳实践:

  • 仅在必要时使用:只有在必须绕过 Java 安全机制并获得更高性能时才考虑使用 Unsafe。
  • 小心操作:确保在使用 Unsafe 时仔细考虑可能的风险,并采取适当的措施来减少潜在问题。
  • 文档和测试:详细记录 Unsafe 使用情况,并进行充分的测试,以确保应用在各种情况下都能够稳定运行。

5.1 使用案例:CAS 操作

下面是一个使用 Unsafe 进行 CAS(比较并交换)操作的简单案例。CAS 是一种常见的并发控制手段,可用于线程安全的更新变量。

import sun.misc.Unsafe;

public class CasExample {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset(CasExample.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    private volatile int value = 0;

    public void increment() {
        int current;
        do {
            current = unsafe.getIntVolatile(this, valueOffset);
        } while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1));
    }

    public int getValue() {
        return value;
    }
}

6. 结论

Java 中的 Unsafe 类为开发者提供了一种强大而危险的工具,可以用于在某些特定情况下实现高性能的操作。然而,使用 Unsafe 也需要开发者对风险有清晰的认识,并采取适当的措施来确保应用的稳定性和安全性。

请注意,在使用 Unsafe 时需要格外小心,遵循最佳实践,以免引发不必要的问题和风险。
var code = “f51e287c-e22a-4dae-941c-8535bb5c6f9a”

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
UnsafeJava一个非常特殊的,它提供了对底层操作的支持,包括内存操作、线程调度等。使用Unsafe可以实现一些Java无法实现的操作,并且可以提高代码的性能。 CAS(Compare and Swap)操作是Unsafe非常常见的操作之一。它的作用是比较当前值和期望值是否相等,如果相等,则将新值更新到当前值,否则不做任何操作。CAS操作通常被用于多线程编程的数据同步,以保证多个线程对共享变量的操作是正确的。 下面是使用Unsafe进行CAS操作的一个示例: ``` import sun.misc.Unsafe; import java.lang.reflect.Field; public class CASDemo { private static Unsafe unsafe; static { try { // 使用反射获取Unsafe Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { // 假设初始值为10 int expectedValue = 10; // 将初始值存储在内存地址100的位置 long offset = unsafe.objectFieldOffset(CASDemo.class.getDeclaredFields()[0]); // 使用CAS操作将值从10更新为20 boolean casResult = unsafe.compareAndSwapInt(new CASDemo(), offset, expectedValue, 20); System.out.println(casResult); } private int value = 10; } ``` 在上面的代码,首先通过反射获取Unsafe的实例,然后将初始值存储在的某个字段,接着使用`unsafe.compareAndSwapInt()`方法进行CAS操作,将值从10更新为20。这个方法的第一个参数是对象实例,第二个参数是字段的内存地址偏移量,第三个参数是期望值,第四个参数是更新值。如果更新成功,该方法返回true,否则返回false。 需要注意的是,Unsafe使用需要非常小心,因为它可以直接操作内存,容易引发安全问题。在使用Unsafe的时候,应该先了解清楚相关的API文档,并且在实际应用要进行严格的测试和验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JAVA开发区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值