什么是Unsafe?谈谈你对unsafe的理解?

面试题~

2 个回答

  • 吴敏
    你爱你的玫瑰是因为你在你的玫瑰上花了太多的时间!
    ### 对 Unsafe 类的理解 #### 前言 `Unsafe` 是位于 `sun.misc` 包下的一个类,主要提供了一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等。这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。但由于 `Unsafe` 类使 Java 拥有了类似 C 语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。过度、不正确地使用 `Unsafe` 类会使得程序出错的概率变大,使得 Java 这种安全的语言变得不再“安全”,因此对 `Unsafe` 的使用一定要慎重。 本文将对 `sun.misc.Unsafe` 的公共 API 功能及相关应用场景进行介绍。 #### 功能介绍 `Unsafe` 提供的 API 大致可分为内存操作、CAS、Class 相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类,下面将对其相关方法和应用场景进行详细介绍。 ##### 内存操作 这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。 ```java // 分配内存,相当于 C++ 的 malloc 函数 public native long allocateMemory(long bytes); // 扩充内存 public native long reallocateMemory(long address, long bytes); // 释放内存 public native void freeMemory(long address); // 在给定的内存块中设置值 public native void setMemory(Object o, long offset, long bytes, byte value); // 内存拷贝 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); // 获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar 等 public native Object getObject(Object o, long offset); // 为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt, putDouble,putLong,putChar 等 public native void putObject(Object o, long offset, Object x); // 获取给定地址的 byte 类型的值(当且仅当该内存地址为 allocateMemory 分配时,此方法结果为确定的) public native byte getByte(long address); // 为给定地址设置 byte 类型的值(当且仅当该内存地址为 allocateMemory 分配时,此方法结果才是确定的) public native void putByte(long address, byte x); ``` 通常,我们在 Java 中创建的对象都处于堆内内存(heap)中,堆内内存是由 JVM 所管控的 Java 进程内存,并且它们遵循 JVM 的内存管理机制,JVM 会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存,存在于 JVM 管控之外的内存区域。Java 中对堆外内存的操作,依赖于 `Unsafe` 提供的操作堆外内存的 native 方法。 ##### 使用堆外内存的原因 1. **对垃圾回收停顿的改善**:由于堆外内存是直接受操作系统管理而不是 JVM 管理,所以当我们使用堆外内存时,可以保持较小的堆内内存规模,从而在 GC 时减少回收停顿对于应用的影响。 2. **提升程序 I/O 操作的性能**:通常在 I/O 通信过程中,会存在堆内内存到堆外内存的数据拷贝操作。对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。 #### 典型应用 `DirectByteBuffer` 是 Java 用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在 Netty、MINA 等 NIO 框架中应用广泛。`DirectByteBuffer` 对于堆外内存的创建、使用、销毁等逻辑均由 `Unsafe` 提供的堆外内存 API 来实现。 创建 `DirectByteBuffer` 时,通过 `Unsafe.allocateMemory` 分配内存、`Unsafe.setMemory` 进行内存初始化,而后构建 `Cleaner` 对象用于跟踪 `DirectByteBuffer` 对象的垃圾回收,以实现当 `DirectByteBuffer` 被垃圾回收时,分配的堆外内存一起被释放。 ##### Cleaner 的实现机制 `Cleaner` 继承自 Java 四大引用类型之一的虚引用 `PhantomReference`。众所周知,无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生 GC 的时候,其均可被回收。通常 `PhantomReference` 与引用队列 `ReferenceQueue` 结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。 当某个被 `Cleaner` 引用的对象将被回收时,JVM 垃圾收集器会将此对象的引用放入到对象引用中的 `pending` 链表中,等待 `Reference-Handler` 进行相关处理。`Reference-Handler` 为一个拥有最高优先级的守护线程,会循环不断地处理 `pending` 链表中的对象引用,执行 `Cleaner` 的 `clean` 方法进行相关清理工作。 当 `DirectByteBuffer` 仅被 `Cleaner` 引用(即为虚引用)时,其可以在任意 GC 时段被回收。当 `DirectByteBuffer` 实例对象被回收时,在 `Reference-Handler` 线程操作中,会调用 `Cleaner` 的 `clean` 方法根据创建 `Cleaner` 时传入的 `Deallocator` 来进行堆外内存的释放。 ![Cleaner 的实现机制](https://images.codewd.com/2c1021ee11f21ac4e915fb565a116e6a.png) ### 参考资料 - [谈谈 Java 魔法类 Unsafe](https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html) 通过以上介绍,可以看出 `Unsafe` 类虽然提供了强大的底层操作能力,但也需要谨慎使用,以避免不必要的内存问题和程序崩溃风险。
    编辑于 2023-12-26 04:47 •IP属地 北京
    举报
    评论
    收藏1

  • 武如风
    首先你要快乐,其次都是其次。
    ### **什么是 Unsafe?** **`Unsafe`** 是 Java 提供的一个底层类,位于 `sun.misc` 包中,允许直接操作内存、对象字段等底层资源。它是 Java 中实现高性能并发操作和底层功能的重要工具,但因其操作不安全且复杂,通常只在 JDK 内部和一些高性能框架中使用。 ### **Unsafe 的核心功能** 1. **直接操作内存**: - `allocateMemory`:分配堆外内存。 - `freeMemory`:释放堆外内存。 - `copyMemory`:复制内存数据。 - 这些方法允许开发者直接操作内存,绕过垃圾回收机制。 2. **CAS(Compare-And-Swap)操作**: - 提供了硬件级别的原子操作,支持 `compareAndSwapInt`、`compareAndSwapLong` 等方法。 - 是 Java 并发工具包(`java.util.concurrent`)的核心实现机制。 3. **线程操作**: - 提供 `park` 和 `unpark` 方法,用于阻塞或唤醒线程。 - 是实现锁和同步工具(如 `LockSupport`)的基础。 4. **对象操作**: - 直接获取对象的内存地址、字段偏移量。 - 支持无视访问控制(如直接修改 `private` 字段)。 5. **栅栏(Fence)操作**: - 提供内存屏障方法(如 `loadFence`、`storeFence`),用于保证内存可见性。 - 是高性能并发操作的重要工具。 ### **使用 Unsafe 的注意事项** 1. **获取 Unsafe 实例**: - 由于 `Unsafe` 的构造方法是私有的,无法直接实例化。 - 通过反射可以获取: ```java Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafeField.get(null); ``` - 如果直接调用 `Unsafe.getUnsafe()`,非引导类加载器会抛出 `SecurityException`。 2. **操作非常危险**: - 可以直接操作内存,可能导致程序崩溃。 - 绕过 JVM 的安全检查,可能导致不可预测的行为。 ### **典型使用场景** 1. **CAS 操作实现**: - `AtomicInteger` 等类基于 `Unsafe` 实现原子性。 - 示例代码: ```java private static final Unsafe unsafe = getUnsafeInstance(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } } private volatile int value; public void increment() { int current; do { current = value; } while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1)); } ``` 2. **堆外内存分配**: - 用于分配和管理堆外内存,提高性能: ```java long memoryAddress = unsafe.allocateMemory(1024); unsafe.putInt(memoryAddress, 42); // 写入数据 int value = unsafe.getInt(memoryAddress); // 读取数据 unsafe.freeMemory(memoryAddress); // 释放内存 ``` 3. **线程挂起和恢复**: - 用于实现线程的阻塞和唤醒: ```java unsafe.park(false, 0L); // 挂起当前线程 unsafe.unpark(thread); // 唤醒指定线程 ``` ### **Unsafe 的局限性和未来** 1. **不安全性**: - 直接操作内存,可能导致程序崩溃或内存泄漏。 - 绕过 Java 的访问控制,可能引发潜在的安全问题。 2. **即将移除**: - 从 JDK 9 开始,推荐使用 `VarHandle` 和 `MemorySegment` 替代 `Unsafe`。 - JDK 23 将进一步限制对 `Unsafe` 的使用。 ### **替代方案** 1. **`VarHandle`(JDK 9 引入)**: - 提供对字段、数组元素等的内存访问功能。 - 支持原子性操作、内存可见性控制。 - 示例: ```java VarHandle handle = MethodHandles.lookup() .findVarHandle(Counter.class, "value", int.class); handle.compareAndSet(counter, 0, 1); ``` 2. **`MemorySegment`(JDK 22 引入)**: - 提供更安全的堆外内存管理功能。 - 支持自动释放内存,避免内存泄漏。 ### **总结** `Unsafe` 是 Java 的一个底层工具类,提供了直接操作内存、原子操作和线程控制等功能。虽然其功能强大,但因不安全且难以使用,逐渐被更安全的替代方案取代。对于开发者来说,应尽量避免直接使用 `Unsafe`,转而使用如 `VarHandle` 和 `MemorySegment` 等现代化工具。
    发布于 2022-12-28 04:05 •IP属地 云南
    举报
    评论
    收藏