JUC 学习笔记 1:Unsafe 类

1、简介

Unsafe 类是在 sun.misc 包中,使 Java 拥有了类似 C 语言指针操作内存的功能。

该类使用了单例模式,需要通过静态方法 getUnsafe() 来获取 theUnsafe 实例。

如果不是由 Java 启动类加载器加载的类调用 getUnsafe() 方法,会抛出 SecurityException 异常,源码如下:

private static final Unsafe theUnsafe = new Unsafe();

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

开发者可以使用 Java 反射技术来获取 theUnsafe 实例,代码如下:

try {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
}

2、方法介绍

2.1、操作内存

2.1.1、获取内存页大小
public native int pageSize();

pageSize() 方法返回操作系统内存页的字节数,例如 4096。

2.1.2、分配内存
public native long allocateMemory(long size);

allocateMemory() 方法分配一块参数 size 指定大小的内存空间,这块内存块可能包含有垃圾数据(没有初始化清零)。如果分配失败则抛出 java.lang.OutOfMemoryError。该方法返回一个非 0 的内存地址。

2.1.3、重新分配内存
public native long reallocateMemory(long address, long size);

reallocateMemory() 方法重新分配一块参数 size 指定大小的内存空间,将数据从参数 address 指定的内存块拷贝到新分配的内存块。如果参数 address 的值为 0 则和 allocateMemory() 方法相同。

2.1.4、设置内存
public native void setMemory(Object obj, long offset, long bytes, byte value);

setMemory() 方法将参数 objoffset 指定地址开始的 bytes 个字节的内存块的值设置为参数 value,一般用于分配内存后对内存块进行初始化清零。

public class User {
    // 声明为long类型而不能是Long类型
    protected long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
}

Field idField = User.class.getDeclaredField("id");
long idFiledOffset = unsafe.objectFieldOffset(idField);

User user = new User();
user.setId(0x0102030405060708L);
unsafe.setMemory(user, idFiledOffset, 5, (byte) 1);
// Java字节序为ByteOrder.LITTLE_ENDIAN,低位在前
// 输出:01 02 03 01 01 01 01 01
System.out.println("user.getId()===" + user.getId());
2.1.5、读取和写入内存
  • getXXX(long address) 方法
  • putXXX(long address, XXX value) 方法

XXX 包括 8 种基本类型 boolean、byte、short、char、int、long、float、double 和引用类型 Object。

// 获取指针长度?值为4或8
public native int addressSize();
public native long getAddress(long address);
public native void putAddress(long address, long v);
// long
public native long getLong(long address);
public native void putLong(long address, long v);
2.1.6、拷贝内存
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
2.1.7、释放内存
public native void freeMemory(long address);

freeMemory() 方法释放 allocateMemory()reallocateMemory() 方法分配的内存空间。如果参数 address 为 0 则什么也不做。

2.1.8、示例代码 DirectByteBuffer
DirectByteBuffer(int cap) {
    // int mark, int pos, int lim, int cap
    super(-1, 0, cap, cap);
    // 检查JVM是否需要对堆外内存进行对齐
    boolean pa = VM.isDirectMemoryPageAligned();
    // 获取内存页大小
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    // 检查已使用堆外内存容量大小totalCapacity是否超过限制-XX:MaxDirectMemorySize
    // CAS设置totalCapacity、reservedMemory
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        // 1.分配内存
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        // 如果内存分配失败,恢复分配内存前的totalCapacity、reservedMemory
        Bits.unreserveMemory(size, cap);
        // 抛出OutOfMemoryError
        throw x;
    }
    // 2.初始化内存清零
    unsafe.setMemory(base, size, (byte) 0);
    // 3.内存对齐
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    // 4.创建内存释放任务Cleaner和Deallocator
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

private static class Deallocator implements Runnable {
    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        // 释放内存
        unsafe.freeMemory(address);
        address = 0;
        // 恢复分配内存前的totalCapacity、reservedMemory
        Bits.unreserveMemory(size, capacity);
    }
}

2.2、内存屏障

public native void loadFence();
public native void storeFence();
public native void fullFence();

2.3、操作类

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<?> cls);
public native void ensureClassInitialized(Class<?> cls);

2.4、操作对象

2.4.1、实例化对象
public native Object allocateInstance(Class<?> var1) throws InstantiationException;

allocateInstance() 方法在实例化对象的时候,会触发类的加载和初始化,但是并不会调用构造方法。常用于反序列化过程中,既保证安全又可以节省时间。

User user = (User) unsafe.allocateInstance(User.class);
System.out.println(user);

// 输出结果:
// this is static block
// User{id=0, name='null'}
@Data
public class User {

    static int STATIC_INT;

    static {
        STATIC_INT = 10;
        System.out.println("this is static block");
    }

    protected long id;

    private String name;

    private User() {
        System.out.println("this is private constructor without arg");
    }

    public User(long id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("this is constructor with args");
    }
}
2.4.2、获取属性的偏移
// 获取静态属性的Object,即Class对象
public native Object staticFieldBase(Field var1);
// 获取静态属性的偏移
public native long staticFieldOffset(Field var1);
// 获取非静态属性的偏移
public native long objectFieldOffset(Field var1);

示例代码:

// 输出对象布局
System.out.println(ClassLayout.parseInstance(user).toPrintable());
// 输出属性偏移
for (Field f : User.class.getDeclaredFields()) {
    if ((Modifier.STATIC & f.getModifiers()) != 0) {
        System.out.println(f.getName()
            + " 属性对应的内存地址偏移 : "
            + unsafe.staticFieldOffset(f));
    } else {
        System.out.println(f.getName()
            + " 属性对应的内存地址偏移 : "
            + unsafe.objectFieldOffset(f));
    }
}
2.4.2.1、指针压缩

开启对象指针压缩 -XX:-UseCompressedClassPointers
开启类型指针压缩 -XX:+UseCompressedOops
对象头占用 8 字节 Mark Word + 4 字节 Class Pointer = 12 字节
实例属性占用 4 字节 String 类型 + 8 字节 long 类型 = 12 字节
对齐填充占用 0 字节

org.example.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String User.name                                 null
     16     8               long User.id                                   0
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

STATIC_INT 属性对应的内存地址偏移 : 104
id 属性对应的内存地址偏移 : 16
name 属性对应的内存地址偏移 : 12

开启对象指针压缩 -XX:-UseCompressedClassPointers
关闭类型指针压缩 -XX:+UseCompressedOops
对象头占用 8 字节 Mark Word + 8 字节 Class Pointer = 16 字节
实例属性占用 8 字节 long 类型 + 4 字节 String 类型 = 12 字节
对齐填充占用 4 字节

org.example.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           b0 45 7d 9d (10110000 01000101 01111101 10011101) (-1652734544)
     12     4                    (object header)                           7a 02 00 00 (01111010 00000010 00000000 00000000) (634)
     16     8               long User.id                                   0
     24     4   java.lang.String User.name                                 null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

STATIC_INT 属性对应的内存地址偏移 : 104
id 属性对应的内存地址偏移 : 16
name 属性对应的内存地址偏移 : 24

关闭对象指针压缩 -XX:-UseCompressedOops
关闭类型指针压缩 -XX:-UseCompressedClassPointers
对象头占用 8 字节 Mark Word + 8 字节 Class Pointer = 16 字节
实例属性占用 8 字节 long 类型 + 8 字节 String 类型 = 16 字节
对齐填充占用 0 字节

org.example.User object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           b0 45 a4 ca (10110000 01000101 10100100 11001010) (-895203920)
     12     4                    (object header)                           a5 02 00 00 (10100101 00000010 00000000 00000000) (677)
     16     8               long User.id                                   0
     24     8   java.lang.String User.name                                 null
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

STATIC_INT 属性对应的内存地址偏移 : 160
id 属性对应的内存地址偏移 : 16
name 属性对应的内存地址偏移 : 24
2.4.3、读取和写入对象属性
2.4.3.1、getXXX/putXXX
  • getXXX(Object obj, long offset)
  • putXXX(Object obj, long offset, XXX value)

XXX 包括 8 种基本类型 boolean、byte、short、char、int、long、float、double 和引用类型 Object。

  • 对于静态属性,参数 obj 可以通过 staticFieldBase() 方法获取,参数 offset 可以通过 staticFieldOffset() 方法获取,也可以直接通过 User.class 获取。
  • 对于非静态属性,参数 obj 需要开发者自己创建,参数 offset 可以通过 objectFieldOffset() 方法获取。
// Object
public native Object getObject(Object obj, long offset) {
public native void putObject(Object obj, long offset, Object v);
// long
public native long getLong(Object obj, long offset);
public native void putLong(Object obj, long offset, long v);
2.4.3.1、getXXXVolatile/putXXXVolatile
  • getXXXVolatile(Object obj, long offset)
  • putXXXVolatile(Object obj, long offset, XXX value)

XXX 包括 8 种基本类型 boolean、byte、short、char、int、long、float、double 和引用类型 Object。

// Object
public native Object getObjectVolatile(Object obj, long offset);
public native void putObjectVolatile(Object obj, long offset, Object v);
// long
public native long getLongVolatile(Object obj, long offset);
public native void putLongVolatile(Object obj, long offset, long v);

引自 openjdk\src\hotspot\share\oops\access.hpp

MO_SEQ_CST: Sequentially consistent stores.

  • The stores are observed in the same order by MO_SEQ_CST loads on other processors
  • Preceding loads and stores in program order are not reordered with subsequent loads and stores in program order
  • Guarantees from releasing stores hold
// putXXX
oop x = JNIHandles::resolve(x_h);
oop p = JNIHandles::resolve(obj);
assert_field_offset_sane(p, offset);
HeapAccess<ON_UNKNOWN_OOP_REF>::oop_store_at(p, offset, x);
// putXXXVolatile
oop x = JNIHandles::resolve(x_h);
oop p = JNIHandles::resolve(obj);
assert_field_offset_sane(p, offset);
HeapAccess<MO_SEQ_CST | ON_UNKNOWN_OOP_REF>::oop_store_at(p, offset, x);
2.4.3.2、putOrderedXXX

putOrderedXXX() 方法,XXX 包括基本类型 int、long 和引用类型 Object。

public native void putOrderedObject(Object obj, long offset, Object x);
public native void putOrderedInt(Object obj, long offset, int x);
public native void putOrderedLong(Object obj, long offset, long x);

2.5、操作数组

2.5.1、获取数组基本偏移
public native int arrayBaseOffset(Class arrayClass);

arrayBaseOffset() 方法获取给定数组中第 1 个元素的内存地址偏移量。

2.5.2、获取数组比例因子
public native int arrayIndexScale(Class arrayClass);

Reports the scale factor for addressing elements in the storage allocation of a given array class. However, arrays of “narrow” types will generally not work properly with accessors like {@link #getByte(Object, long)}, so the scale factor for such classes is reported as zero.

arrayIndexScale() 方法获取给定数组中每个元素内存地址的比例因子,获取不到则返回 0。

// indexScale=8
int indexScale = unsafe.arrayIndexScale(long[].class);
// indexScale=4
int indexScale = unsafe.arrayIndexScale(int[].class);

arrayBaseOffset() 方法配合 arrayIndexScale() 方法使用可以获取数组中每个元素在内存中的位置。

2.5.3、示例代码 ConcurrentHashMap
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {

    private static final sun.misc.Unsafe U;
    private static final long ABASE;
    private static final int ASHIFT;

    static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ConcurrentHashMap.class;
            // ...
            Class<?> ak = Node[].class;
            ABASE = U.arrayBaseOffset(ak);
            int scale = U.arrayIndexScale(ak);
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    static final <K,V> boolean casTabAt(Node<K,V>[] tab,
                                        int i,
                                        Node<K,V> c,
                                        Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }
}

2.6、操作异常

/** Throws the exception without telling the verifier. */
public native void throwException(Throwable e);

2.7、操作线程

2.7.1、阻塞线程
public native void park(boolean isAbsolute, long time);

park() 方法阻塞当前线程,直到

  1. unpark() 方法被调用
  2. 线程被中断
  3. 参数 time 指定时间超时

等条件出现。

JVM 采用 pthread_cond_wait 或 pthread_cond_timedwait 实现。

参数 isAbsolute 如果为 true 则表示参数 time 采用绝对时间,代表最后期限的毫秒数时间戳;否则采用相对时间,代表经过的纳秒数。

如果调用 unpark() 方法后紧接着调用 park() 方法,将不会阻塞线程。

2.7.2、唤醒线程
public native void unpark(Object thread);

unpark() 方法可以唤醒指定线程。JVM 采用 pthread_cond_signal 实现。

整个并发框架中,除了

  • JDK1.5 引入的 Exchanger
  • JDK1.7 引入的 ForkJoinPool
  • JDK1.8 引入的 StampedLock

对线程的阻塞和唤醒操作被封装在 LockSupport 类中,LockSupport 类中有各种重载的 park() 方法,但最终都调用了 UNSAFE.park() 方法。


2.8、CAS操作

public final Object getAndSetObject(Object obj, long offset, Object newValue)
public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update)

public final int getAndSetInt(Object obj, long offset, int newValue)
public final int getAndAddInt(Object obj, long offset, int delta)
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update)

public final long getAndSetLong(Object obj, long offset, long newValue)
public final long getAndAddLong(Object obj, long offset, long delta)
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update)
参数类型作用
objObject待操作的对象
offsetlong待更新字段的内存地址偏移量
expectObject/int/long待更新字段的期望值
updateObject/int/long如果期望值和待更新字段的实际值一致,则将待更新字段的内存值更新为 update
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值