当前位置: 首页 > article >正文

技术总结(三十三)

一、Unsafe 介绍

Unsafe 是位于 sun.misc 包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。但由于 Unsafe 类使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用 Unsafe 类会使得程序出错的概率变大,使得 Java 这种安全的语言变得不再“安全”,因此对 Unsafe 的使用一定要慎重。

另外,Unsafe 提供的这些功能的实现需要依赖本地方法(Native Method)。你可以将本地方法看作是 Java 中使用其他编程语言编写的方法。本地方法使用 native 关键字修饰,Java 代码中只是声明方法头,具体的实现则交给 本地代码

为什么要使用本地方法呢?

  1. 需要用到 Java 中不具备的依赖于操作系统的特性,Java 在实现跨平台的同时要实现对底层的控制,需要借助其他语言发挥作用。
  2. 对于其他语言已经完成的一些现成功能,可以使用 Java 直接调用。
  3. 程序对时间敏感或对性能要求非常高时,有必要使用更加底层的语言,例如 C/C++甚至是汇编。

在 JUC 包的很多并发工具类在实现并发机制时,都调用了本地方法,通过它们打破了 Java 运行时的界限,能够接触到操作系统底层的某些功能。对于同一本地方法,不同的操作系统可能会通过不同的方式来实现,但是对于使用者来说是透明的,最终都会得到相同的结果。

二、Java 中如何安全地使用 Unsafe 类?

在 Java 中,虽然Unsafe类(sun.misc.Unsafe)并非是面向普通开发者公开使用的类,但在一些特定的高性能、底层开发场景下确实需要使用它时,可以通过以下相对安全的方式来操作:

1. 获取 Unsafe 实例

  • 反射方式获取(常用)
    由于Unsafe类的构造函数是私有的,不能直接通过new的方式创建实例,通常采用反射机制来获取它的实例。以下是一个示例代码片段:
import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class UnsafeUtil {
    private static final Unsafe unsafe;

    static {
        try {
            Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            unsafe = (Unsafe) theUnsafeField.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("获取Unsafe实例失败", e);
        }
    }

    public static Unsafe getUnsafe() {
        return unsafe;
    }
}
  • 首先通过Unsafe.class.getDeclaredField("theUnsafe")尝试获取Unsafe类中名为theUnsafe的私有字段,这个字段实际就是其单例实例的引用。
  • 然后调用theUnsafeField.setAccessible(true)来绕过 Java 语言正常的访问控制限制(因为是私有字段),使得可以访问该字段。
  • 最后通过(Unsafe) theUnsafeField.get(null)获取到Unsafe的实例,这里传入null是因为theUnsafe字段是静态的,不需要具体的对象实例去获取它。

2. 谨慎使用内存操作相关方法

  • 内存分配
    Unsafe类提供了像allocateMemory方法用于直接分配指定大小的内存空间,示例如下:
long address = unsafe.allocateMemory(1024); // 分配1024字节的内存
try {
    // 在这里可以对分配的内存进行后续操作,比如写入数据等
} finally {
    unsafe.freeMemory(address); // 操作完成后一定要记得释放内存,避免内存泄漏
}

要注意的是,一旦分配了内存,后续必须要在合适的时候通过freeMemory方法来释放所分配的内存,否则很容易造成内存泄漏,就像在使用普通的 Java 对象时如果没有释放资源一样,只是这里是更底层的直接内存操作,更需要严格遵循释放原则。

  • 对象内存布局修改
    例如可以使用objectFieldOffset方法来获取对象中某个字段的内存偏移量,进而可以通过偏移量来操作字段的值,如下代码演示:
class MyObject {
    private int myValue;
}

MyObject obj = new MyObject();
long offset = unsafe.objectFieldOffset(MyObject.class.getDeclaredField("myValue"));
unsafe.putInt(obj, offset, 10); // 将obj对象中myValue字段的值设置为10
int value = unsafe.getInt(obj, offset); // 获取obj对象中myValue字段的值

在这个过程中,一定要确保获取的偏移量准确无误,并且操作时严格按照对象的实际内存布局和字段类型来进行,不然容易破坏对象的正常结构,导致不可预期的行为,比如读取到错误的数据或者程序出现错误。

三、Unsafe 类的使用场景有哪些?

1. 高性能并发数据结构实现

  • 无锁队列
    在多线程环境下,普通的队列如果要保证线程安全,使用锁机制(如synchronized关键字或者ReentrantLock等可重入锁)来控制并发访问会带来一定的性能开销,尤其是在高并发场景下,频繁地获取和释放锁会导致线程阻塞、上下文切换等情况,降低系统整体性能。而利用Unsafe类提供的原子操作(比如compareAndSwapIntcompareAndSwapLong等比较并交换操作,简称CAS操作),可以实现无锁的队列。例如,通过CAS操作来实现队列元素入队和出队时对队列头、尾指针的原子性更新,避免了使用传统锁机制,提高了并发处理能力,像著名的ConcurrentLinkedQueue(虽然它是 Java 标准库中已经实现好的,但其底层原理就是利用类似机制)就运用了基于CAS的无锁思想来高效地支持多线程并发操作。
  • 原子操作类的实现补充
    Java 中的java.util.concurrent.atomic包下有很多原子操作类,如AtomicIntegerAtomicLong等,它们内部在一定程度上依赖Unsafe类来实现更底层的原子操作。比如在AtomicInteger类中,对整型变量的原子性自增、自减以及获取并更新等操作,除了基于 Java 语言层面的一些机制外,最终往往会借助Unsafe类提供的CAS操作来确保在多线程环境下操作的原子性,保证不同线程对同一个原子变量操作时数据的一致性,从而实现高效且安全的并发访问控制。

2. 直接内存操作

  • 内存分配与释放
    在一些对内存使用效率有特殊要求的场景中,需要绕过 Java 堆内存的管理机制直接分配和管理内存,Unsafe类就提供了这样的能力。例如,像一些高性能的网络编程框架,当需要处理大量的网络数据缓冲区时,使用Unsafe类的allocateMemory方法可以直接按需分配内存空间,避免了经过 Java 堆内存分配时可能存在的额外开销(如垃圾回收的影响等)。在分配完内存后,后续可以使用对应的freeMemory方法及时释放内存,像下面这样的简单示例:
import sun.misc.Unsafe;

public class DirectMemoryExample {
    private static final Unsafe unsafe;

    static {
        try {
            Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            unsafe = (Unsafe) theUnsafeField.get(null);
        } catch (Exception e) {
            throw new RuntimeException("获取Unsafe实例失败", e);
        }
    }

    public static void main(String[] args) {
        long address = unsafe.allocateMemory(1024); // 分配1024字节的内存
        try {
            // 在这里可以对分配的内存进行后续操作,比如写入一些数据等
        } finally {
            unsafe.freeMemory(address); // 操作完成后一定要记得释放内存,避免内存泄漏
        }
    }
}
  • 内存复制与填充
    有时候需要快速地在内存中进行数据的复制或者填充操作,Unsafe类的copyMemory方法可以实现将一段内存中的数据复制到另一段内存区域,在处理一些底层数据结构或者批量数据处理场景中很有用。例如,将一个大的数据块从内存的一个区域快速移动到另一个区域,提高数据处理效率;还有setMemory方法可以按照指定的字节值对一段内存区域进行填充,比如将一块新分配的内存区域初始化为全 0 等操作,方便后续对内存的使用管理。

http://www.kler.cn/a/402726.html

相关文章:

  • UG NX二次开发(C++)-UIStyler-指定平面的对象和参数获取
  • 神经网络的初始化
  • 基于STM32的火灾报警装置的Proteus仿真
  • Spring Boot与MyBatis-Plus的高效集成
  • 【大语言模型】ACL2024论文-20 SCIMON:面向新颖性的科学启示机器优化
  • 若依-一个请求中返回多个表的信息
  • Flink使用详解
  • 【5】GD32H7xx CAN发送及FIFO接收
  • React 远程仓库拉取项目部署,无法部署问题
  • 【VTK】MFC中使用VTK9.3
  • 1+X应急响应(网络)威胁情报分析:
  • 百度遭初创企业指控抄袭,维权还是碰瓷?
  • Github 2024-11-20C开源项目日报 Top9
  • 【Python项目】基于Python的医疗知识图谱问答系统
  • 低代码开发平台搭建思考与实战
  • ftdi_sio应用学习笔记 2 - 操作串口
  • Linux系统之lsblk命令的基本使用
  • 音视频入门基础:MPEG2-TS专题(7)——FFmpeg源码中,读取出一个transport packet数据的实现
  • 芯原科技嵌入式面试题及参考答案
  • React的诞生与发展
  • AI 大模型:重塑软件开发的魔法力量
  • 【AIGC半月报】AIGC大模型启元:2024.11(下)
  • js utils 封装
  • 快速理解python中的yield关键字
  • Web应用安全入门:架构搭建、漏洞分析与HTTP数据包处理
  • 基于Spark3.4.4开发StructuredStreaming读取文件数据