前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC之Unsafe类

JUC之Unsafe类

作者头像
小四的技术之旅
发布2022-07-26 08:02:38
7910
发布2022-07-26 08:02:38
举报
文章被收录于专栏:小四的技术文章

相关背景

Java 不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe 类提供了硬件级别的原子操作。Unsafe 类使用 private 修饰构造方法,只能使用他自己提供的一个 final 类来进行获取。

代码语言:javascript
复制
private static native void registerNatives();
static {
    registerNatives();
    sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}

private Unsafe() {}

private static final Unsafe theUnsafe = new Unsafe();
//获取unsafe实例的单例方法
@CallerSensitive
public static Unsafe getUnsafe() {
    Class<?> caller = Reflection.getCallerClass();
    //只有由主类加载器(BootStrap classLoader)加载的类才能调用这个类中的方法
    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
        throw new SecurityException("Unsafe");
    return theUnsafe;
}

Unsafe 类提供 102 个 native 方法,主要包含类、对象、变量的管理(获取和修改),内存管理,线程管理,内存屏障等。

代码语言:javascript
复制
public static Unsafe getUnsafe() throws Exception{
    //1.最简单的使用方式是基于反射获取Unsafe实例
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
    return unsafe;
}

类、对象、变量管理

getObject

代码语言:javascript
复制
public native Object getObject(Object o, long offset);

该方法用于获取 Java 对象 o 中内存地址偏移量为 offerset 的对象。类似的方法还有 getInt()、getDouble 等。

Object o:引用对象 long offset:内存偏移量(偏移量是可以通过 objectFieldOffset 方法获取获取的)

代码语言:javascript
复制
public native long objectFieldOffset(Field f);
public native long staticFieldOffset(Field f);
public native int arrayBaseOffset(Class<?> arrayClass);

操作案例

代码语言:javascript
复制
@Data
public class UnsafeTest {

    //类占8位
    private int id;//占4位
    private String sex;//对象占4位
    private String name;//offset=20

    public static Unsafe getUnsafe() throws Exception{
        //1.最简单的使用方式是基于反射获取Unsafe实例
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        return unsafe;
    }

    public static void main(String[] args) throws Exception {
        Unsafe unsafe = getUnsafe();
        UnsafeTest hello = new UnsafeTest();
        hello.name = "mikey";
        Field field = UnsafeTest.class.getDeclaredField("name");
        long offset = unsafe.objectFieldOffset(field);
        System.out.println("offset="+offset);
        Object name = unsafe.getObject(hello, offset);
        System.out.println(name);
    }
}

putObject

该方法用于修改 Java 对象 o 中内存地址偏移量为 offerset 的对象。类似的方法还有 putInt()、putDouble 等。

代码语言:javascript
复制
public native void putObject(Object o, long offset, Object x);

Object o:待更新的对象 long offset:对象熟悉的偏移地址 Object x:更新后的值

代码语言:javascript
复制
@Data
public class UnsafeTest {

    //类占8位
    private int id;//占4位
    private String sex;//对象占4位
    private String name;//offset=20

    public static Unsafe getUnsafe() throws Exception{
        //1.最简单的使用方式是基于反射获取Unsafe实例
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        return unsafe;
    }

    public static void main(String[] args) throws Exception {
        Unsafe unsafe = getUnsafe();
        UnsafeTest hello = new UnsafeTest();
        hello.name = "mikey";
        Field field = UnsafeTest.class.getDeclaredField("name");
        long offset = unsafe.objectFieldOffset(field);
        System.out.println("offset="+offset);
        Object name = unsafe.getObject(hello, offset);
        System.out.println(name);
        unsafe.putObject(hello,offset,"leo");
        System.out.println(hello.name);
    }
}

getObjectVolatile

getObjectVolatile 方法用于获取对象 o 带有 volatile 关键字修饰的属性,和 getObject 方法类似。

代码语言:javascript
复制
Object getObjectVolatile(Object o, long offset);

putObjectVolatile

putObjectVolatile 方法用于设置对象 o 带有 volatile 关键字修饰的属性,和 putObject 方法类似。

代码语言:javascript
复制
/**
* Stores a reference value into a given Java variable, with
* volatile store semantics. Otherwise identical to putObject(Object, long, Object)
*/
public native void    putObjectVolatile(Object o, long offset, Object x);

putOrderedObject

putObjectVolatile(Object,long,Object)的版本,不保证存储对其他线程的即时可见性。通常只有当底层字段是 volatile(或者如果是数组单元,则只能使用 volatile 访问)时,此方法才有用。

代码语言:javascript
复制
public native void    putOrderedObject(Object o, long offset, Object x);

defineClass

告诉 VM 定义一个类,而不进行安全检查。默认情况下类加载器和保护域来自调用方的类。

代码语言:javascript
复制
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);

defineAnonymousClass

定义一个类,但不告知类加载器和系统字典。类似于我们代码中的 lambda 表达式。

代码语言:javascript
复制
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

staticFieldBase

结合 staticFieldOffset 返回静态字段的位置。获取基“Object”(如果有),通过它可以通过 getInt(Object,long)等方法访问给定类的静态字段。此值可能为空。此值可能引用一个“cookie”对象,不能保证它是真实对象,并且除了用作此类中 get 和 put 例程的参数外,不应以任何方式使用它。

代码语言:javascript
复制
public native Object staticFieldBase(Field f);

shouldBeInitialized

检测给定类是否需要初始化。这通常是需要与获取类别。仅当对 ensureClassifilized 的调用无效时返回 false。

代码语言:javascript
复制
public native boolean shouldBeInitialized(Class<?> c);

ensureClassInitialized

该方法用于确保给定类已初始化。这通常需要与获取类的静态字段基一起使用。

代码语言:javascript
复制
public native void ensureClassInitialized(Class<?> c);

内存管理

获取偏移地址

代码语言:javascript
复制
public native long objectFieldOffset(Field f);//获取对象的字段(属性)偏移地址
public native long staticFieldOffset(Field f);//获取对象的静态字段(属性)偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);//获取数组的字段(属性)偏移地址
public native int arrayIndexScale(Class<?> arrayClass);//可以获取数组中元素间的偏移地址增量

addressSize

获取本机指针字节大小。该值将为 4 或 8。

代码语言:javascript
复制
public native int addressSize();

pageSize

获取本地内存的页数,该值为 2 的幂次方

代码语言:javascript
复制
public native int pageSize();

getAddress/putAddress

从给定内存地址获取本机指针。如果地址为零,或未指向从 allocateMemory 获得的块,则结果未定义。如果本机指针的宽度小于 64 位,则会将其作为无符号数扩展到 Java long。指针可以通过任何给定的字节偏移量进行索引,只需将该偏移量(作为简单整数)添加到表示指针的 long 中即可。从目标地址实际读取的字节数可能由地址大小决定。

代码语言:javascript
复制
public native long getAddress(long address);
public native void putAddress(long address, long x);

通过 Unsafe 类可以分配内存,可以释放内存;

类中提供的 3 个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与 C 语言中的 3 个方法对应。

allocateMemory

分配一个新的给定大小本地内存。内存内容未初始化;它们通常是垃圾。生成的本机指针永远不会为零,并且将针对所有值类型进行对齐。通过调用 freeMemory 来释放此内存,或使用 reallocatemory 调整其大小。

代码语言:javascript
复制
public native long allocateMemory(long bytes);

reallocateMemory

将新的本机内存块大小调整为给定的字节大小。这个超过旧块大小的新块的内容为未初始化;它们通常是垃圾。生成的本机当且仅当请求的大小为零时,指针将为零。这个生成的本机指针将针对所有值类型进行对齐。处置通过调用 freeMemory 或使用 reallocateMemory 调整其大小。其中传递给此方法的 address 可能为 null 在这种情况下,将执行分配。

代码语言:javascript
复制
public native long reallocateMemory(long address, long bytes);

setMemory

将给定内存块中的所有字节设置为固定值(通常为零)。这提供了单寄存器寻址模式。

代码语言:javascript
复制
public void setMemory(long address, long bytes, byte value)

copyMemory

将给定内存块中的所有字节设置为另一个块的副本。此方法通过两个参数确定每个块的基址,因此它(实际上)提供双寄存器寻址模式,如{getInt(Object,long)}中所述。当对象引用为 null 时,偏移量提供一个绝对基址。传输以确定大小的相干(原子)单位进行通过地址和长度参数。如果有效地址和长度均为偶数模 8,传输以“长”单位进行。如果有效地址和长度分别为偶模 4 或 2,传输以“int”或“short”为单位进行。

代码语言:javascript
复制
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);

freeMemory

释放 address 地址的内存空间,如果 address 为空则不做任何处理

代码语言:javascript
复制
public native void freeMemory(long address);

线程管理

线程管理主要提供了对象锁,cas 轻量级锁等功能

monitorEnter

锁住当前对象 o,它必须通过{monitorExit 方法}解锁。可重入锁。

代码语言:javascript
复制
public native void monitorEnter(Object o);

monitorExit

解锁当前对象

代码语言:javascript
复制
public native void monitorExit(Object o);

tryMonitorEnter

尝试锁定对象。返回 true 或 false 以指示锁定是否成功。如果是,则对象必须是通过{monitorExit}解锁。

代码语言:javascript
复制
public native boolean tryMonitorEnter(Object o);

park/unpark

锁住/解锁当前线程。LockSupport 类就是基于这两个方法实现的。

park:除非许可可用,否则出于线程调度目的禁用当前线程。如果许可证用,则会使用该许可并立即返回调用;否则,出于线程调度目的,当前线程将被禁用,并处于休眠状态,直到发生以下三种情况之一:

其他一些线程以当前线程为目标调用 unpark;其他线程中断当前线程;该调用错误地(即无原因地)返回。此方法不报告导致该方法返回的原因。调用者应该首先重新检查导致线程停止的条件。例如,调用者还可以在返回时确定线程的中断状态。

代码语言:javascript
复制
//第一个参数是是否是绝对时间,第二个参数是等待时间值。如果isAbsolute是true则会实现ms定时。如果isAbsolute是false则会实现ns定时。
public native void park(boolean isAbsolute, long time);
//如果线程在park上被阻塞,那么它将解除阻塞。否则,它对park的下一个调用将保证不会阻塞。如果给定的线程尚未启动,则不能保证此操作有任何效果。
public native void unpark(Object thread);

compareAndSwapObject

CAS(compareAndSwapObject 比较并交换)

代码语言:javascript
复制
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

针对 Object 对象进行 CAS 操作。即是对应 Java 变量引用 o,原子性地更新 o 中偏移地址为 offset 的属性的值为 x,当且仅的偏移地址为 offset 的属性的当前值为 expected 才会更新成功返回 true,否则返回 false。

o:目标 Java 变量引用。offset:目标 Java 变量中的目标属性的偏移地址。expected:目标 Java 变量中的目标属性的期望的当前值。x:目标 Java 变量中的目标属性的目标更新值。类似的方法有compareAndSwapInt和compareAndSwapLong,在 Jdk8 中基于 CAS 扩展出来的方法有getAndAddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSetObject,它们的作用都是:通过 CAS 设置新的值,返回旧的值。

CAS 底层实现

代码语言:javascript
复制
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  //获取对象的变量的地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  //调用Atomic操作
  //进入atomic.hpp,大意就是先去获取一次结果,如果结果和现在不同,就直接返回,因为有其他人修改了;否则会一直尝试去修改。直到成功。
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

内存屏障

在 Unsafe 类中提供了三个内存屏障的方法,storeFence、fullFence、loadFence

storeFence

表示该方法之前的所有 store(写)操作在内存屏障之前完成。

代码语言:javascript
复制
public native void storeFence();

loadFence

表示该方法之前的所有 load(读)操作在内存屏障之前完成。

代码语言:javascript
复制
public native void loadFence();

fullFence

表示该方法之前的所有 load、store 操作在内存屏障之前完成

代码语言:javascript
复制
public native void fullFence();

资料 http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/ jdk1.8 sun.misc.Unsafe

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 刘牌 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 相关背景
  • 类、对象、变量管理
    • getObject
      • putObject
        • getObjectVolatile
          • putObjectVolatile
            • putOrderedObject
              • defineClass
                • defineAnonymousClass
                  • staticFieldBase
                    • shouldBeInitialized
                      • ensureClassInitialized
                      • 内存管理
                        • 获取偏移地址
                          • addressSize
                            • pageSize
                              • getAddress/putAddress
                                • allocateMemory
                                  • reallocateMemory
                                    • setMemory
                                      • copyMemory
                                        • freeMemory
                                        • 线程管理
                                          • monitorEnter
                                            • monitorExit
                                              • tryMonitorEnter
                                                • park/unpark
                                                  • compareAndSwapObject
                                                  • CAS 底层实现
                                                  • 内存屏障
                                                    • storeFence
                                                      • loadFence
                                                        • fullFence
                                                        领券
                                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档