浅析Java中的Unsafe类

Unsafe类

Unsafe类是 Java 中一些进行底层操作和不安全操作的方法集合,提供了一些可以直接操控内存和线程的低层次操作。这个不安全的类提供了一个观察 HotSpot JVM 内部结构并且可以对其进行修改。有时它可以被用来在不适用 C++ 调试的情况下学习虚拟机内部结构,有时也可以被拿来做性能监控和开发工具。

本篇仅简单介绍,不涉及复杂使用和底层原理。

1. 获取Unsafe实例

private static final Unsafe theUnsafe = new Unsafe();

public static Unsafe getUnsafe() {
    return theUnsafe;
}

Unsafe类提供了一个十分朴素的get方法,用于获取一个static finalUnsafe实例。

2. 获取当前系统信息

2.1 获取本机地址大小和页面大小

public int addressSize() {
    return ADDRESS_SIZE;
}

public int pageSize() { 
	return PAGE_SIZE; 
}

addressSize()方法和pageSize()方法分别可以获取本机的地址大小(实际即为字长)和页面大小。

2.2 获取本机是大端编址/小端编址

public final boolean isBigEndian() { return BIG_ENDIAN; }

2.3 检查是否可以在不对齐地址上访问

/**
 * @return Returns true if this platform is capable of performing
 * accesses at addresses which are not aligned for the type of the
 * primitive type being accessed, false otherwise.
 */
public final boolean unalignedAccess() { return UNALIGNED_ACCESS; }

3. allocate分配方法

3.1 allocateInstance方法

@HotSpotIntrinsicCandidate
public native Object allocateInstance(Class<?> cls)
    throws InstantiationException;

allocateInstance方法可以创建一个cls参数对应类型的对象,同时不调用任何构造函数。

3.2 allocateUninitializedArray方法

public Object allocateUninitializedArray(Class<?> componentType, int length) {
   if (componentType == null) {
       throw new IllegalArgumentException("Component type is null");
   }
   if (!componentType.isPrimitive()) {
       throw new IllegalArgumentException("Component type is not primitive");
   }
   if (length < 0) {
       throw new IllegalArgumentException("Negative length");
   }
   return allocateUninitializedArray0(componentType, length);
}

@HotSpotIntrinsicCandidate
private Object allocateUninitializedArray0(Class<?> componentType, int length) {
    // These fallbacks provide zeroed arrays, but intrinsic is not required to
    // return the zeroed arrays.
    if (componentType == byte.class)    return new byte[length];
    if (componentType == boolean.class) return new boolean[length];
    if (componentType == short.class)   return new short[length];
    if (componentType == char.class)    return new char[length];
    if (componentType == int.class)     return new int[length];
    if (componentType == float.class)   return new float[length];
    if (componentType == long.class)    return new long[length];
    if (componentType == double.class)  return new double[length];
    return null;
}

allocateUninitializedArray()方法可以分配一个指定大小,但未进行初始化的数组。未进行归零的结果是这个未初始化数组中都是各种无意义的垃圾数据。

3.3 allocateMemory方法

public long allocateMemory(long bytes) {
    allocateMemoryChecks(bytes);

    if (bytes == 0) {
        return 0;
    }

    long p = allocateMemory0(bytes);
    if (p == 0) {
        throw new OutOfMemoryError();
    }

    return p;
}

private native long allocateMemory0(long bytes);

allocateMemory()方法分配一个新的本机内存块,内存内容未初始化,一般是垃圾数据。

3.4 freeMemory方法

public void freeMemory(long address) {
    freeMemoryChecks(address);

    if (address == 0) {
        return;
    }

    freeMemory0(address);
}

private native void freeMemory0(long address);

freeMeomory()方法释放由allocateMemory()申请的一个已分配的内存块。

3.5 reallocateMememory方法

public long reallocateMemory(long address, long bytes) {
    reallocateMemoryChecks(address, bytes);

    if (bytes == 0) {
        freeMemory(address);
        return 0;
    }

    long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes);
    if (p == 0) {
        throw new OutOfMemoryError();
    }

    return p;
}

reallocateMemory()方法可以修改一个由allocate()方法申请的内存块的大小。如果修改后的大小比原块小,那么截断。如果修改后的大小比原块大,那么超出的空间将是一片垃圾数据。

4. 数组元素定位方法

public int arrayIndexScale(Class<?> arrayClass) {
    if (arrayClass == null) {
        throw new NullPointerException();
    }

    return arrayIndexScale0(arrayClass);
}

private native int arrayIndexScale0(Class<?> arrayClass);

arrayIndexScale()方法可以获取参数arrayClass的对应比例因子,可以和偏移量结合定位到数组中某个元素。

public int arrayBaseOffset(Class<?> arrayClass) {
    if (arrayClass == null) {
        throw new NullPointerException();
    }

    return arrayBaseOffset0(arrayClass);
}

private native int arrayBaseOffset0(Class<?> arrayClass);

arrayBaseOffset()方法可以获取参数arrayClass的对应数组中,第一个元素的偏移量。可以同上面的arrayIndexScale()方法解和定位到数组中某个元素的地址。

5. byte与bool相互转换方法

@ForceInline
private boolean byte2bool(byte b) {
    return b != 0;
}

@ForceInline
private byte bool2byte(boolean b) {
return b ? (byte)1 : (byte)0;
}

Unsafe类提供了在bool和byte之间转换的方法。

注:@ForceInline注解强制要求不对这个方法进行方法内联。

6. CAS方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DIqq4k5x-1635390819612)(G:\markdown\Java\知识点\image-20211027145455143.png)]Unsafe类中给出了大量比较并设置和比较并交换方法。这些方法都指向某个native的方法作为底层实现。

且这些方法都由@HotSpotIntrinsicCandidate所标记。

7. 内存块拷贝方法

7.1 copyMeomry方法

public void copyMemory(long srcAddress, long destAddress, long bytes) {
    copyMemory(null, srcAddress, null, destAddress, bytes);
}

public void copyMemory(Object srcBase, long srcOffset,
                       Object destBase, long destOffset,
                       long bytes) {
    copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes);

    if (bytes == 0) {
        return;
    }

    copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes);
}

@HotSpotIntrinsicCandidate
private native void copyMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);

copyMemory()方法将指定内存块中的内容在内存的另一个位置设置一个拷贝。

7.2 copySwapMemory方法

public void copySwapMemory(long srcAddress, long destAddress, long bytes, long elemSize) {
    copySwapMemory(null, srcAddress, null, destAddress, bytes, elemSize);
}

public void copySwapMemory(Object srcBase, long srcOffset,
                           Object destBase, long destOffset,
                           long bytes, long elemSize) {
    copySwapMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes, elemSize);

    if (bytes == 0) {
        return;
    }

    copySwapMemory0(srcBase, srcOffset, destBase, destOffset, bytes, elemSize);
}

private native void copySwapMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize);

copySwapMemory()方法将指定内存块中的内容与内存中另一个位置中的内容进行交换。

8. 定义类方法

8.1 defineAnonymousClass方法

public Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches) {
    if (hostClass == null || data == null) {
        throw new NullPointerException();
    }
    if (hostClass.isArray() || hostClass.isPrimitive()) {
        throw new IllegalArgumentException();
    }

    return defineAnonymousClass0(hostClass, data, cpPatches);
}

defineAnonymousClass方法可以创建一个不被虚拟机及系统字典所知的类。

8.2 defineClass方法

public Class<?> defineClass(String name, byte[] b, int off, int len,
                            ClassLoader loader,
                            ProtectionDomain protectionDomain) {
    if (b == null) {
        throw new NullPointerException();
    }
    if (len < 0) {
        throw new ArrayIndexOutOfBoundsException();
    }

    return defineClass0(name, b, off, len, loader, protectionDomain);
}

public native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                    ClassLoader loader,
                                    ProtectionDomain protectionDomain);

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

9. 清理IO缓冲区

public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
    if (!directBuffer.isDirect())
        throw new IllegalArgumentException("buffer is non-direct");

    DirectBuffer db = (DirectBuffer) directBuffer;
    if (db.attachment() != null)
        throw new IllegalArgumentException("duplicate or slice");

    Cleaner cleaner = db.cleaner();
    if (cleaner != null) {
        cleaner.clean();
    }
}

10. 添加内存屏障

@HotSpotIntrinsicCandidate
public native void loadFence();

@HotSpotIntrinsicCandidate
public native void storeFence();

loadFence()方法可以添加一个 Load - Load 型的内存屏障。

storeFence()方法可以添加一个 Store - Store 型的内存屏障。

参考《Java并发编程的艺术》p26

11. 通过对象和偏移量直接获取对应数据的get方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MwgDgmb5-1635390819616)(G:\markdown\Java\知识点\image-20211027154806221.png)]

Unsafe类中提供了大量的get方法,可以通过对象和偏移量,定位获取到对应的值。并同时做addswap等操作。下面是简单的示例。

// getAddress方法可以获取对应位置的本机指针
@ForceInline
public long getAddress(Object o, long offset) {
    if (ADDRESS_SIZE == 4) {
        return Integer.toUnsignedLong(getInt(o, offset));
    } else {
        return getLong(o, offset);
    }
}

@HotSpotIntrinsicCandidate
public native int getInt(Object o, long offset);

@HotSpotIntrinsicCandidate
public final int getAndSetInt(Object o, long offset, int newValue) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, newValue));
    return v;
}

@ForceInline
public final int getAndBitwiseAndInt(Object o, long offset, int mask) {
    int current;
    do {
        current = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset,
                                   current, current & mask));
    return current;
}

12. 通过对象和偏移量直接向指定内存位置赋值的put方法

image-20211028110310148

Unsafe类提供的put方法可以通过对象和偏移量,向指定的位置赋值。下面是简单的示例

@ForceInline
public void putAddress(Object o, long offset, long x) {
    if (ADDRESS_SIZE == 4) {
        putInt(o, offset, (int)x);
    } else {
        putLong(o, offset, x);
    }
}

@HotSpotIntrinsicCandidate
public native void putInt(Object o, long offset, int x);

@HotSpotIntrinsicCandidate
public native void putReference(Object o, long offset, Object x);

13. 用指定字符填充内存

public void setMemory(Object o, long offset, long bytes, byte value) {
    setMemoryChecks(o, offset, bytes, value);

    if (bytes == 0) {
        return;
    }

    setMemory0(o, offset, bytes, value);
}

private native void setMemory0(Object o, long offset, long bytes, byte value);

setMemory()方法可以用指定的字符填充一块内存。

posted @ 2021-10-28 11:15  pedro7  阅读(299)  评论(0编辑  收藏  举报