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”

posted on   JAVA开发区  阅读(217)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示