9

并发笔记-JUC之Unsafe类

 2 years ago
source link: https://mikeygithub.github.io/2022/05/25/yuque/eff1dm/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

并发笔记-JUC之Unsafe类 - 麦奇

并发笔_

Mikey 2022年5月25日 下午

1.6k 字

22 分钟

2 次

image.png

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

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 方法,主要包含类、对象、变量的管理(获取和修改),内存管理,线程管理,内存屏障等。

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

public native Object getObject(Object o, long offset);

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

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

public native long objectFieldOffset(Field f);
public native long staticFieldOffset(Field f);
public native int arrayBaseOffset(Class<?> arrayClass);
@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 等。

public native void putObject(Object o, long offset, Object x);

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

@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 方法类似。

Object getObjectVolatile(Object o, long offset);

putObjectVolatile

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

/**
* 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 访问)时,此方法才有用。

public native void    putOrderedObject(Object o, long offset, Object x);

获取偏移地址

public native long objectFieldOffset(Field f);//获取对象的字段(属性)偏移地址
public native long staticFieldOffset(Field f);//获取对象的静态字段(属性)偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);//获取数组的字段(属性)偏移地址

defineClass

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

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

defineAnonymousClass

定义一个类,但不要让类加载器或系统字典知道它。
对于每个 CP 条目,相应的 CP 修补程序必须为 null 或具有

*与标记匹配的 a 格式:

*Integer、Long、Float、Double:java 中相应的包装对象类型。朗

*Utf8:字符串(如果用作签名或名称,则必须具有合适的语法)

*类:任何 java。lang.类对象

*String:任何对象(不仅仅是 java.lang.String)

*InterfaceMethodRef:(NYI)在调用站点的参数上调用的方法句柄

*@用于链接、访问控制、保护域和类加载器的 params hostClass 上下文

*@类文件的 params 数据字节

cpPatches:如果存在非 null 条目,则替换数据中相应的 CP 条目

public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

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

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

3、getObject
通过给定的 Java 变量获取引用值。这里实际上是获取一个 Java 对象 o 中,获取偏移地址(内存地址)为 offset 的属性的值,此方法可以突破修饰符的抑制,也就是无视 private、protected 和 default 修饰符。类似的方法有 getInt、getDouble 等等。

根据偏移量计算哪个值需要更改,一般一个 class 类默认开头占 8 个字节,然后每个 int 会占 4 个字节,这个偏移量一般为 12 或者 16 获取其它字节数字段,偏移量的获取一般使用 objectFieldOffset(Field var1)默认方法获取,然后进行值得更改。

4、CAS(compareAndSwapObject 比较并交换)

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 变量中的目标属性的目标更新值。

类似的方法有compareAndSwapIntcompareAndSwapLong,在 Jdk8 中基于 CAS 扩展出来的方法有getAndAddIntgetAndAddLonggetAndSetIntgetAndSetLonggetAndSetObject,它们的作用都是:通过 CAS 设置新的值,返回旧的值。

https://www.cnblogs.com/throwable/p/9139947.html
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK