了解java并发包或nio底层的都应该知道Unsafe这个类,如并发包的锁,通过Unsafe#park() 和Unsafe#unPark()来实现线程阻塞和恢复运行的,这个类没有公布源码,但是有很多比较有用的方法,它可以直接操作内存,使用的时候务必要谨慎,不小心可能会造成内存泄漏。
实现浅克隆思路
为了表述方便,用S代表要克隆的对象,D表示克隆后的对象,SD表示S的内存地址,DD表示D的内存地址,SIZE表示该对象在内存中的大小。
1,获取原对象的所在的内存地址SD
2,计算原对象在内存中的大小SIZE
3,新分配一块内存,大小为原对象大小SIZE,记录新分配内存的地址DD。
4,从原对象内存地址SD处复制大小为SIZE的内存,复制到DD处
5,DD处的SIZE大小的内存就是原对象的浅克隆对象,强制转换为源对象类型就可以了
unsafe方法介绍
unsafe有很多比较牛逼的方法,刚开始接触可能会感到不可思议,下面介绍几个比较实用的方法。
1,Object allocateInstance(Class aClass)
这个方法是分配一个实例,它的牛逼之处在于,只要是非abstract的类它都能实例化,即使这个类没有public的构造方法,它甚至能绕过各种JVM安全检查,不运行任何构造方法,当你用这个方法初始化类后,通过getClassLoader()方法视图去获取他的classloader会发现它返回的是null。是不是很神奇
2,long objectFieldOffset(Field f)
获取对象属性偏移量
3,int arrayBaseOffset(Class arrayClass)
这个方法是返回一个数组第一个元素的偏移量,这个可以用在获取对象内存地址的时候,因为unsafe没有提供直接获取对象实例内存地址的方法,只有获取通过对象属性偏移量获取属性内存地址的方法,所以我们可以通过构建一个数组对象,通过数组元素偏移量获取元素的内存地址,可以参考后面的代码。
4,long allocateMemory(long bytes)
这个方法是直接分配一个bytes大小的内存,然后返回内存的起始地址。
5,long getLong(Object o, long offset)
获取对象o的偏移量为offset位置的long值,这个long值是该位置后64位二进制的long值,同样的方法还有getInt,getByte等
6, putLong(Object o, long offset, long x);
把long x的2进制值放在对象o的offset偏移量的位置。
offset偏移量:是只相对于另一个地址便宜的byte数;
获取Unsafe实例
Unsafe这个类是在sun.misc包下,构造器是私有的,提供一个getUnsafe()的方法获取单例,仅供java类库的类使用,即只能是BootstrapClassLoader加载器加载的类使用,源码如下:
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class cc = Reflection.getCallerClass();
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
可以通过反射的方式获取它的实例:
Field theUnsafe = null;
try {
theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
1,获取原对象的内存地址
unsafe类没有提供直接获取实例对象内存地址的方法,但是可以间接获取,大概思路是:构建一个新对象N,N包含了S对象的引用,只要获取到N对S的引用地址就是S的内存地址了;
unsafe类获取内存地址的方法只有一个,就是getLong(Object o, long offset);为了方便,可以构建一个包含S的Object[]数组,获取到Object[]的S引用就可以了,代码如下:
private static long getAddr(Object obj) {
Object[] array = new Object[]{obj};
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
return unsafe.getLong(array, baseOffset);
}
2,计算原对象在内存中的大小
我们新建一个对象实例后,jvm做的其实只是在堆中分配非static的Field的内存,其他的static属性,或者方法在加载期间就已经放到内存中去了,所以当我们计算对象大小时只要计算field的大小就行了,jvm分配内存时单个实例中的每个field内存都是连续的,所以我们只需要获得最大偏移量的Field的偏移量,然后加上这个field的大小就可以了,代码如下:
public static long sizeOf(Class> clazz) {
long maximumOffset = 0;
Class> maxiNumFieldClass = null;
do {
for (Field f : clazz.getDeclaredFields()) {
if (!Modifier.isStatic(f.getModifiers())) {
long tmp = unsafe.objectFieldOffset(f);
if(tmp>maximumOffset){
maximumOffset = unsafe.objectFieldOffset(f);
maxiNumFieldClass = f.getType();
}
}
}
} while ((clazz = clazz.getSuperclass()) != null);
long last = byte.class.equals(maxiNumFieldClass)?1:
( short.class.equals(maxiNumFieldClass) || char.class.equals(maxiNumFieldClass))?2:
(long.class.equals(maxiNumFieldClass)||double.class.equals(maxiNumFieldClass))?8:4;
return maximumOffset + last;
}
3,新分配一块大小为SIZE的内存
Unsafe提供了方法 long allocateMemory(long bytes); 可直接分配一块内存,返回内存地址。
4,从原对象内存地址SD处复制大小为SIZE的内存到DD位置
见Unsafe方法:void copyMemory(long srcAddress, long destAddress, long bytes)
5,DD处的SIZE大小的内存赋值给目标对象
Unsafe没有提供直接读内存转为java对象的方法,但是可以通过类似获取对象内存地址的方法来实现:
先新建一个包含S类型属性的对象,让后把DD的内存地址赋值给S类型的属性变量就可以了:
private static T fromAddress(long addr, long size) {
Object[] array = new Object[]{null};
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
unsafe.putLong(array, baseOffset, addr);
return (T) array[0];
}
克隆方法
如果是数组,这个方法就不适用了,因为数组比较特殊,数组类是jvm在运行时动态生成的,有兴趣可以去研究下jvm对数组的处理。最终的克隆方法代码如下:
public static T shallowClone(T t) throws InstantiationException {
Class clazz = t.getClass();
if(clazz.isArray()){
Object[] os = (Object[])t;
return (T)Arrays.copyOf(os,os.length);
}
long srcAddr = getAddr(t);
long size = sizeOf(clazz);
long destAddr = unsafe.allocateMemory(size);
unsafe.copyMemory(srcAddr, destAddr, size);
return fromAddress(destAddr, size);
}
测试
Object s = new Foo(8,888L,"test")
Object s2 = shallowClone(s);
Assert.assertEquals(s, s2);
Assert.assertTrue(s != s2);
ps:用Unsafe分配的内存不在jvm管理的范围内,所以,jvm不会自动去回收这一块内存,你得通过Unsafe#freeMemory(long address) 去释放这块的内存。
分享到:
相关推荐
Java的Unsafe类是一个强大的工具,它位于rt.jar包中,提供了原子级别的操作,这些操作都是通过JNI(Java Native Interface)直接调用本地C++库实现的。Unsafe的存在是为了应对高并发环境下的数据同步问题,其核心...
在Java编程中,sun.misc.UnSafe类是一个非常特殊的存在。这个类在JDK8中扮演着一个核心的角色,它提供了对Java语言规范中未公开的底层操作的访问。尽管UnSafe类并非设计为公共API的一部分,但它因其强大的功能而被...
这些操作在Java并发库的`LockSupport`类中被封装,`LockSupport.park()`和`LockSupport.unpark(Thread)`都是基于`Unsafe`实现的。这种机制在实现自定义同步原语时非常有用。 4. CAS(Compare and Swap)操作: CAS...
Java中的`Unsafe`类是一个非常底层的工具类,它提供了对内存操作的直接访问,类似于C语言中的指针。由于其强大的能力,`Unsafe`类能够执行一些常规Java API无法完成的任务,但也正因为这种能力,它也可能带来安全...
Java中的`Unsafe`类是一个非常特殊的工具类,它位于`sun.misc`包下,不属于Java标准库的一部分。尽管如此,由于其强大的底层操作能力,它在许多高性能的Java框架和库中扮演着重要角色,例如Netty、Hadoop和Kafka。`...
Java中unsafe操作是Java无锁操作的基石,在无锁并发类中都少不了它们的身影,比如ConcurrentHashMap、ConcurrentLinkedQueue等都是由Unsafe类来实现的。Unsafe类提供了多种操作,包括compareAndSwap、putOrder等,...
虽然Oracle发行的JDK版本不包含Unsafe的源代码,但在并发编程中,Unsafe类为java.util.concurrent包里的类提供了底层支持,例如通过提供绕过JVM直接修改内存的能力和使用硬件CPU指令实现CAS(Compare-And-Swap)原子...
总之,`Unsafe`类提供了对Java虚拟机的底层访问,能够执行一些常规编程无法实现的操作。然而,由于其潜在的危险性和不稳定性,开发者应谨慎使用,仅在必要时考虑,并确保对其有深入理解。在大多数情况下,使用Java...
java魔法类:Unsafe应用
本文深入解析Java并发编程中的两个关键类:Atomic和Unsafe。这些类在提高Java并发操作的效率和安全性方面扮演着至关重要的角色。原子操作的核心:原子操作是不可分割的操作单元,确保数据的一致性和完整性。Java通过...
Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本...
基于令牌桶算法的Java限流实现 在软件系统中,限流机制是一个重要的环节,它可以防止系统资源被过度使用,避免系统崩溃或性能下降。常见的限流算法有多种,如漏桶算法、令牌桶算法、滑动窗口算法等。在Java中,我们...
JDK8中sun.misc包下的UnSafe类,想查看源码的就拿走,没积分的请与我联系!xtfggef@gmail.com
在 Java 中,CAS 操作主要在 JUC 中的 atomic 包,通过 Unsafe 类实现。Unsafe 类提供了一系列增加 Java 语言能力的操作,如内存管理、操作类/对象/变量、多线程同步等。Unsafe 类中的方法都是 native 方法,由 JVM ...
在Java并发编程中,LockSupport和Unsafe是两个关键的工具类,它们提供了底层的线程控制功能,使得开发者能够深入地管理和控制线程的行为。LockSupport是Java并发库中的一个核心工具类,它提供了线程的阻塞和唤醒功能...
Java多线程与并发处理是Java编程中的高级话题,涉及到JUC(java.util.concurrent)包中的原子类、CAS(Compare-And-Swap)机制、Unsafe类以及多线程并发的无锁方案和线程安全的实现方法。 CAS是一种无锁的同步机制...
Java的`Unsafe`类是Java语言中一个强大的工具,它提供了对内存的直接访问和控制,包括在堆外分配内存、执行无同步的字节码操作等。然而,由于其潜在的安全风险和易用性的缺乏,`Unsafe`通常被封装在高级库中供开发者...
在Java编程语言中,`sun.misc.Unsafe`类是一个神秘而强大的工具,它提供了对内存的直接操作和访问,绕过了Java的一些安全限制。这个类通常不被推荐在生产环境中直接使用,因为它的使用涉及到底层内存操作,可能会...
这个Unsafe类允许直接访问JVM中的内存,这是非常危险的,但是很有趣:)。 unsafe-helper-包含一些简单的方法,这些方法使使用sun.misc.Unsafe更容易。 unsafe-collection-在ArrayList上建模的示例列表,该列表不...
在Java并发编程领域,Unsafe类和LockSupport类是两个重要的底层工具类,它们提供了低级别的内存操作和线程控制,使得开发者能够实现高效的并发算法和数据结构。本文将深入探讨这两个类的源码,理解它们的工作原理和...