首先聊聊反射
java和golang都有各自的反射机制,为什么标准库会提供反射机制呢?
反射(reflection)允许程序在运行时(runtime)检查、修改程序(比如对象,struct等)的结构与行为,跳过编译检查,越过访问权限,运行时对象生成,方法调用等。如果没有反射,那么需要完全手动进行硬编码,比如如果没有反射,那么在spring的ioc容器管理实现就需要我们使用new来创建对象,那么也就不叫spring ioc,不会有spring ioc的诞生了。
静态编译(多数静态语言):在编译时确定类型,绑定对象。
动态编译(多数动态语言):运行时确定类型,绑定对象。可以实现动态创建对象和编译,体现出很大的灵活性(特别是在J2EE的开发中它的灵活性就表现的十分明显)。通过反射机制我们可以获得类的各种内容。对于java以及golang这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。
反射的实现
在java和golang的反射实现中,主要依赖于对象指针(也就是对象的内存地址),以及编译时已链接到对象上的强类型信息。
BTW,在java语言的反射Method.invoke方法调用实现中,默认为委派实现,委派给本地方法来进行方法调用。在调用超过15次之后,委派实现便会将委派对象切换至动态实现(自己动态生成字节码),动态实现和本地实现相比,其运行效率要快上20倍,因为本地实现需要先调c++本地实现,然后c++的返回再用java接收,所以是比较耗时的。另外,生成动态实现的字节码也是比较耗时的,当然生成动态调用的字节码只生成一次,生成后就可以多次调用。
为何需要unsafe
在golang的标准包中,提供了unsafe操作(unsafe.go),通过主要用于对内存指针的操作,普通对象指针通过转化为unsafe.Pointer,而unsafe.Pointer可以转化为uintptr,uintptr支持指针相关的内存操作,通过unsafe包,间接的实现了直接指针内存相关的操作.
而在java的标准包中,并没有提供unsafe包,unsafe包存在于sun.msic下的Unsafe.java类,misc即miscellaneous混杂的意思,因为unsafe会破坏java倡导的内存安全,所以一直没有并入标准包,另外,oracle目前在主导unsafe包的移除和替换工作,java中的unsafe包,同样提供了基于内存地址的相关操作,除了操作GC能回收的堆内存,还能操作GC无法回收的区域–堆外内存,可以直接对内存地址的值进行操作,新建的对象需要自己手动free释放内存,目前的线程挂起和恢复,cas原子操作,类实例化以及基于内存偏移地址的修改,在许多的库中有广泛的使用,比如java中整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。java的unsafe包主要可以提高程序的运行性能,以及可以减少GC的垃圾回收停顿时间,对一些java应用性能要求较高的应用,提供了除JNI C++调用之外的另一种选择
java unsafe参考示例
- 实例化私有类 正常情况下没法实例化一个私有构造函数的类,但通过反射或unsafe可以做到。区别在于unsafe不会调用构造函数初始化,以及成员变量的初始化,如下:
package com.anteoy.coreJava.unsafe; import sun.misc.Unsafe; import java.lang.reflect.Field; /** * @auther zhoudazhuang * @date 19-3-22 12:51 * @description */ public class Uncover { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException { Field f = null; f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); System.out.println(unsafe); test2(unsafe); } static void test1(Unsafe unsafe) throws IllegalAccessException, InstantiationException { A o1 = new A(); // constructor System.out.println(o1.a()); // prints 1 A o2 = A.class.newInstance(); // reflection System.out.println(o2.a()); // prints 1 // 不会赋值 不会初始化不会构造函数 A o3 = (A) unsafe.allocateInstance(A.class); // unsafe System.out.println(o3.a());// prints 0 } static void test2(Unsafe unsafe) throws NoSuchFieldException { Guard guard = new Guard(); guard.giveAccess(); // false, no access // bypass Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED"); //过内存偏移地址修改变量值 unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption guard.giveAccess(); // true, access granted } } class A { private long a = 10; // not initialized value public A() { System.out.println("init"); this.a = 1; // initialization } public long a() { return this.a; } } class Guard { private int ACCESS_ALLOWED = 1; public boolean giveAccess() { System.out.println(42 == ACCESS_ALLOWED); return 42 == ACCESS_ALLOWED; } }
- cas原子级操作&&通过内存偏移地址修改变量值
注意代码中的对应的内存偏移地址,需要根据打印出的地址自己调整,因为可能因为操作系统不一致,导致内存地址的偏移量不相同
package com.anteoy.coreJava.unsafe; import sun.misc.Unsafe; import java.lang.reflect.Field; /** * @auther zhoudazhuang * @date 19-3-22 17:05 * @description */ public class UnsafePlayer { public static void main(String[] args) throws Exception { //通过反射实例化Unsafe Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); //实例化Player Player player = (Player) unsafe.allocateInstance(Player.class); player.setAge(18); player.setName("li lei"); for(Field field:Player.class.getDeclaredFields()){ System.out.println(field.getName()+":对应的内存偏移地址"+unsafe.objectFieldOffset(field)); } System.out.println("-------------------"); int ageOffset= 12; //修改内存偏移地址为12的值(age),返回true,说明通过内存偏移地址修改age的值成功 System.out.println(unsafe.compareAndSwapInt(player, ageOffset, 18, 20)); System.out.println("age修改后的值:"+player.getAge()); System.out.println("-------------------"); //修改内存偏移地址为12的值,但是修改后不保证立马能被其他的线程看到。 unsafe.putOrderedInt(player, 12, 33); System.out.println("age修改后的值:"+player.getAge()); System.out.println("-------------------"); //修改内存偏移地址为12的值,volatile修饰,修改能立马对其他线程可见 unsafe.putObjectVolatile(player, 12, "han mei"); System.out.println("name修改后的值:"+unsafe.getObjectVolatile(player, 12)); } } class Player{ private int age; private String name; private Player(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
- 阻塞和恢复
package com.anteoy.coreJava.unsafe; /** * @auther zhoudazhuang * @date 19-3-22 17:24 * @description */ import java.util.concurrent.locks.LockSupport; public class Lock { public static void main(String[] args) throws InterruptedException { ThreadPark threadPark = new ThreadPark(); threadPark.start(); ThreadUnPark threadUnPark = new ThreadUnPark(threadPark); threadUnPark.start(); //等待threadUnPark执行成功 threadUnPark.join(); System.out.println("运行成功...."); } static class ThreadPark extends Thread{ public void run(){ System.out.println(Thread.currentThread() +"我将被阻塞在这了60s...."); //阻塞60s,单位纳秒 1s = 1000000000 LockSupport.parkNanos(1000000000l*60); System.out.println(Thread.currentThread() +"我被恢复正常了...."); } } static class ThreadUnPark extends Thread{ public Thread thread = null; public ThreadUnPark(Thread thread){ this.thread = thread; } public void run(){ System.out.println("提前恢复阻塞线程ThreadPark"); //恢复阻塞线程 LockSupport.unpark(thread); } } }
golang unsafe参考示例
- 通过指针修改结构体字段
package main import ( "fmt" "unsafe" ) func main() { s := struct { a byte b byte c byte d int64 }{0, 0, 0, 0} // 将结构体指针转换为通用指针 p := unsafe.Pointer(&s) // 保存结构体的地址备用(偏移量为 0) up0 := uintptr(p) // 将通用指针转换为 byte 型指针 pb := (*byte)(p) // 给转换后的指针赋值 *pb = 10 // 结构体内容跟着改变 fmt.Println(s) // 偏移到第 2 个字段 up := up0 + unsafe.Offsetof(s.b) // 将偏移后的地址转换为通用指针 p = unsafe.Pointer(up) // 将通用指针转换为 byte 型指针 pb = (*byte)(p) // 给转换后的指针赋值 *pb = 20 // 结构体内容跟着改变 fmt.Println(s) // 偏移到第 3 个字段 up = up0 + unsafe.Offsetof(s.c) // 将偏移后的地址转换为通用指针 p = unsafe.Pointer(up) // 将通用指针转换为 byte 型指针 pb = (*byte)(p) // 给转换后的指针赋值 *pb = 30 // 结构体内容跟着改变 fmt.Println(s) // 偏移到第 4 个字段 up = up0 + unsafe.Offsetof(s.d) // 将偏移后的地址转换为通用指针 p = unsafe.Pointer(up) // 将通用指针转换为 int64 型指针 pi := (*int64)(p) // 给转换后的指针赋值 *pi = 40 // 结构体内容跟着改变 fmt.Println(s) }
- 修改私有字段
package main import ( "fmt" "reflect" "strings" "unsafe" ) func main() { // 创建一个 strings 包中的 Reader 对象 // 它有三个私有字段:s string、i int64、prevRune int sr := strings.NewReader("abcdef") // 此时 sr 中的成员是无法修改的 fmt.Println(sr) // readbyte一次 其中的偏移字段i就会+1 从0开始 b, err := sr.ReadByte() fmt.Printf("%c, %v\n", b, err) // 但是我们可以通过 unsafe 来进行修改 // 先将其转换为通用指针 p := unsafe.Pointer(sr) // 获取结构体地址 up0 := uintptr(p) // 确定要修改的字段(这里不能用 unsafe.Offsetof 获取偏移量,因为是私有字段) if sf, ok := reflect.TypeOf(*sr).FieldByName("i"); ok { // 偏移到指定字段的地址 up := up0 + sf.Offset // 转换为通用指针 p = unsafe.Pointer(up) // 转换为相应类型的指针 pi := (*int64)(p) fmt.Println(*pi) // 对指针所指向的内容进行修改 *pi = 4 // 修改索引 修改索引字段i } // 看看修改结果 fmt.Println(sr) // 看看读出的是什么 b, err = sr.ReadByte() fmt.Printf("%c, %v\n", b, err) }
- 类型转换和修改
package main import ( "fmt" "strings" "unsafe" ) // 定义一个和 strings 包中的 Reader 相同的本地结构体 type Reader struct { s string i int64 prevRune int } func main() { // 创建一个 strings 包中的 Reader 对象 sr := strings.NewReader("abcdef") // 此时 sr 中的成员是无法修改的 fmt.Println(sr) // 我们可以通过 unsafe 来进行修改 // 先将其转换为通用指针 p := unsafe.Pointer(sr) // 再转换为本地 Reader 结构体 pR := (*Reader)(p) // 这样就可以自由修改 sr 中的私有成员了 (*pR).i = 3 // 修改索引 // 看看修改结果 fmt.Println(sr) // 看看读出的是什么 b, err := sr.ReadByte() fmt.Printf("%c, %v\n", b, err) }
参考以及示例参考
- https://my.oschina.net/HJCui/blog/1817978
- https://www.jianshu.com/p/c394436ec9e5?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
- https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
- https://juejin.im/post/5a75a4fb5188257a82110544
- https://leokongwq.github.io/2016/12/31/java-magic-unsafe.html