`
lingzantia
  • 浏览: 150014 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Java Unsafe类实现任意实例浅克隆

    博客分类:
  • Java
 
阅读更多


了解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类的使用.docx

    Java的Unsafe类是一个强大的工具,它位于rt.jar包中,提供了原子级别的操作,这些操作都是通过JNI(Java Native Interface)直接调用本地C++库实现的。Unsafe的存在是为了应对高并发环境下的数据同步问题,其核心...

    JDK8中sun.misc下UnSafe类源代码 UnSafe.java

    在Java编程中,sun.misc.UnSafe类是一个非常特殊的存在。这个类在JDK8中扮演着一个核心的角色,它提供了对Java语言规范中未公开的底层操作的访问。尽管UnSafe类并非设计为公共API的一部分,但它因其强大的功能而被...

    Java Unsafe类1

    这些操作在Java并发库的`LockSupport`类中被封装,`LockSupport.park()`和`LockSupport.unpark(Thread)`都是基于`Unsafe`实现的。这种机制在实现自定义同步原语时非常有用。 4. CAS(Compare and Swap)操作: CAS...

    Java Unsafe类实现原理及测试代码

    Java中的`Unsafe`类是一个非常底层的工具类,它提供了对内存操作的直接访问,类似于C语言中的指针。由于其强大的能力,`Unsafe`类能够执行一些常规Java API无法完成的任务,但也正因为这种能力,它也可能带来安全...

    一篇看懂Java中的Unsafe类

    Java中的`Unsafe`类是一个非常特殊的工具类,它位于`sun.misc`包下,不属于Java标准库的一部分。尽管如此,由于其强大的底层操作能力,它在许多高性能的Java框架和库中扮演着重要角色,例如Netty、Hadoop和Kafka。`...

    Java中unsafe操作实例总结

    Java中unsafe操作是Java无锁操作的基石,在无锁并发类中都少不了它们的身影,比如ConcurrentHashMap、ConcurrentLinkedQueue等都是由Unsafe类来实现的。Unsafe类提供了多种操作,包括compareAndSwap、putOrder等,...

    JDK Unsafe 源码注释

    虽然Oracle发行的JDK版本不包含Unsafe的源代码,但在并发编程中,Unsafe类为java.util.concurrent包里的类提供了底层支持,例如通过提供绕过JVM直接修改内存的能力和使用硬件CPU指令实现CAS(Compare-And-Swap)原子...

    简单谈一谈Java中的Unsafe类

    总之,`Unsafe`类提供了对Java虚拟机的底层访问,能够执行一些常规编程无法实现的操作。然而,由于其潜在的危险性和不稳定性,开发者应谨慎使用,仅在必要时考虑,并确保对其有深入理解。在大多数情况下,使用Java...

    java魔法类:Unsafe应用

    java魔法类:Unsafe应用

    探秘Java并发:Atomic&Unsafe的强大魔法

    本文深入解析Java并发编程中的两个关键类:Atomic和Unsafe。这些类在提高Java并发操作的效率和安全性方面扮演着至关重要的角色。原子操作的核心:原子操作是不可分割的操作单元,确保数据的一致性和完整性。Java通过...

    java Unsafe详细解析

    Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本...

    基于令牌桶算法的Java限流实现

    基于令牌桶算法的Java限流实现 在软件系统中,限流机制是一个重要的环节,它可以防止系统资源被过度使用,避免系统崩溃或性能下降。常见的限流算法有多种,如漏桶算法、令牌桶算法、滑动窗口算法等。在Java中,我们...

    JDK8中sun.misc包下的UnSafe类

    JDK8中sun.misc包下的UnSafe类,想查看源码的就拿走,没积分的请与我联系!xtfggef@gmail.com

    Java CAS底层实现原理实例详解

    在 Java 中,CAS 操作主要在 JUC 中的 atomic 包,通过 Unsafe 类实现。Unsafe 类提供了一系列增加 Java 语言能力的操作,如内存管理、操作类/对象/变量、多线程同步等。Unsafe 类中的方法都是 native 方法,由 JVM ...

    Java并发编程之LockSupport、Unsafe详解.docx

    在Java并发编程中,LockSupport和Unsafe是两个关键的工具类,它们提供了底层的线程控制功能,使得开发者能够深入地管理和控制线程的行为。LockSupport是Java并发库中的一个核心工具类,它提供了线程的阻塞和唤醒功能...

    Java 多线程与并发(8-26)-JUC原子类- CAS, Unsafe和原子类详解.pdf

    Java多线程与并发处理是Java编程中的高级话题,涉及到JUC(java.util.concurrent)包中的原子类、CAS(Compare-And-Swap)机制、Unsafe类以及多线程并发的无锁方案和线程安全的实现方法。 CAS是一种无锁的同步机制...

    UnsafeAdapter:一个工具包,用于协助使用Java Unsafe类来分配和管理本地堆外内存块

    Java的`Unsafe`类是Java语言中一个强大的工具,它提供了对内存的直接访问和控制,包括在堆外分配内存、执行无同步的字节码操作等。然而,由于其潜在的安全风险和易用性的缺乏,`Unsafe`通常被封装在高级库中供开发者...

    sun.misc.Unsafe源码

    在Java编程语言中,`sun.misc.Unsafe`类是一个神秘而强大的工具,它提供了对内存的直接操作和访问,绕过了Java的一些安全限制。这个类通常不被推荐在生产环境中直接使用,因为它的使用涉及到底层内存操作,可能会...

    unsafe:使用sun.misc.Unsafe的各种Java类

    这个Unsafe类允许直接访问JVM中的内存,这是非常危险的,但是很有趣:)。 unsafe-helper-包含一些简单的方法,这些方法使使用sun.misc.Unsafe更容易。 unsafe-collection-在ArrayList上建模的示例列表,该列表不...

    Java并发编程学习之Unsafe类与LockSupport类源码详析

    在Java并发编程领域,Unsafe类和LockSupport类是两个重要的底层工具类,它们提供了低级别的内存操作和线程控制,使得开发者能够实现高效的并发算法和数据结构。本文将深入探讨这两个类的源码,理解它们的工作原理和...

Global site tag (gtag.js) - Google Analytics