Unsafe-Java永远的“神”(二)

简介: Unsafe-Java永远的“神”(二)

订阅专栏

我们平时如何实现浅克隆?


实现Closeable接口

重写close()方法

一、Unsafe实现浅克隆

浅克隆工具类

package com.liziba.unsafe.clone;
import com.liziba.unsafe.UnsafeFactory;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
 * <p>
 *      浅克隆工具类
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/5/26 21:08
 */
public class ShallowCloneUtil {
    /**
     * 获取对象的内存地址
     *
     * @Description
     * Unsafe类没有提供直接获取实例对象内存地址的方法,但是可以通过以下方式间接获取。
     * 构建对象A,A包含了我们需要获取内存地址的B对象的引用,这样只有获取到A对象持有的B对象的引用地址,就可以知道B对象的地址了。
     * 我们可以通过Unsafe类获取内存地址的方法public native long getLong(Object var1, long var2)来获取;
     * 此处我们为了方便,通过数组Object[] 添加Object元素,持有Object的引用
     *
     * @return
     */
    public static Long getAddress(Object obj) {
        Object[] objects = new Object[] {obj};
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        int arrayBaseOffset = unsafe.arrayBaseOffset(Object[].class);
        return unsafe.getLong(objects, arrayBaseOffset);
    }
    /**
     * 获取对象的大小
     *
     * @Dscription
     * Java中实例化一个对象时,JVM会在堆中分配非static的Field的内存,其他的static属性或者method在类加载期间或者JVM启动时已经存放在内存中。
     * 所以我们计算对象的大小的时候只需要求和Field的大小就行了,JVM分配内存时,单个实例对象中的Field内存是连续不断地,
     * 因此我们只需获取最大偏移量Filed的偏移量 + 最大偏移量Filed本身的大小即可
     *
     * Java中基本数据类型所占的字节数
     * byte/boolean     1 字节
     * char/short       2 字节
     * int/float        4 字节
     * long/double      8 字节
     * boolean 理论上占1/8字节,实际上按照1byte处理。
     * Java采用的是Unicode编码,每一个字节占8位,一个字节由8个二进制位组成。
     *
     * @param clazz
     * @return
     */
    public static Long size(Class clazz) {
        // 最后一个Filed的内存偏移量
        long maxOffset = 0;
        Class lastFiledClass = null;
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        do {
            for (Field field : clazz.getDeclaredFields()) {
                if (!Modifier.isStatic(field.getModifiers())) {
                    long tmpOffset = unsafe.objectFieldOffset(field);
                    if (tmpOffset > maxOffset) {
                        maxOffset = tmpOffset;
                        lastFiledClass = field.getType();
                    }
                }
            }
        } while ((clazz = clazz.getSuperclass()) != null);
        // 最后一个Field本身的大小
        int lastFiledSize = (boolean.class.equals(lastFiledClass) || byte.class.equals(lastFiledClass)) ? 1 :
                                (short.class.equals(lastFiledClass) || char.class.equals(lastFiledClass)) ? 2 :
                                     (int.class.equals(lastFiledClass) || float.class.equals(lastFiledClass)) ? 4 :  8 ;
        return maxOffset + lastFiledSize;
    }
    /**
     * 申请一块固定大小的内存空间
     *
     * @Description
     * 通过Unsafe的public native long allocateMemory(long var1);申请一块内存空间。
     *
     * @param bytes  需要申请的内存大小
     * @return
     */
    public static Long allocateMemory(long bytes) {
        return UnsafeFactory.getUnsafe().allocateMemory(bytes);
    }
    /**
     * 从原对象内存地址srcAddr复制大小位size的内存到destAddr地址处
     *
     * @param srcAddr           源地址
     * @param destAddr          目标地址
     * @param size              复制内存大小
     */
    public static void copyMemory(long srcAddr, long destAddr, long size) {
        UnsafeFactory.getUnsafe().copyMemory(srcAddr, destAddr, size);
    }
    /**
     * Unsafe未提供直接读取内存转为Java对象的方法,但是可以通过新建一个包含T类型属性的对象将申请的内存地址赋值给T属性
     *
     * @param addr
     * @param <T>
     * @return
     */
    public static <T> T addressConvertObject(long addr) {
        Object[] objects = new Object[] {null};
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        int baseOffset = unsafe.arrayBaseOffset(Object.class);
        unsafe.putLong(objects, baseOffset, addr);
        return (T)objects[0];
    }
    /**
     * 实现对象的浅克隆
     *
     * @Description
     * 数组的无法通过此方法实现克隆,数组类是在JVM运行时动态生成的
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T shallowClone(T t) {
        Class<?> clazz = t.getClass();
        if (clazz.isArray()) {
            Object[] objects = (Object[]) t;
            return (T) Arrays.copyOf(objects, objects.length);
        }
        Long srcAddr = getAddress(t);
        Long size = size(clazz);
        Long destAddr = allocateMemory(size);
        copyMemory(srcAddr, destAddr, size);
        return addressConvertObject(destAddr);
    }
}

测试

/**
 * <p>
 *      浅克隆测试
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/5/24 23:11
 */
public class ShallowCloneTest {
    public static void main(String[] args){
        SimpleInfo simpleInfo = new SimpleInfo();
        simpleInfo.setId(1);
        System.out.println(simpleInfo.hashCode());
        SimpleInfo clone = ShallowCloneUtil.shallowClone(simpleInfo);
        System.out.println(clone.hashCode());
    }
}

更完善的方法https://blog.csdn.net/zhxdick/article/details/52003123?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242


目录
相关文章
|
SQL 安全 Java
Java错误小全(二)
在编写程序时,要注意及时释放不再使用的对象,避免持有过多的内存资源。可以考虑使用合适的数据结构、合理管理对象的生命周期,并调整Java虚拟机的内存配置来避免内存溢出错误。
68 0
|
6月前
|
Java 编译器
17. 【Java教程】Java 方法
17. 【Java教程】Java 方法
38 0
|
JSON 安全 Java
Java错误小全(一)
确保方法按照声明中指定的返回值类型返回正确的值。如果方法没有返回值,可以使用void关键字表示。如果方法返回类型为非基本类型(如对象或集合),确保返回的是正确的类型对象。
53 0
java202303java学习笔记第二十三天-初识内部类2
java202303java学习笔记第二十三天-初识内部类2
49 0
java202303java学习笔记第三十八天同步代码块2
java202303java学习笔记第三十八天同步代码块2
46 0
java202303java学习笔记第三十八天同步代码块1
java202303java学习笔记第三十八天同步代码块1
44 0
java202303java学习笔记第二十三天-初识内部类1
java202303java学习笔记第二十三天-初识内部类1
62 0
java202303java学习笔记第二十三天-成员内部类4
java202303java学习笔记第二十三天-成员内部类4
51 0
java202303java学习笔记第二十三天-成员内部类2
java202303java学习笔记第二十三天-成员内部类2
59 0
java202303java学习笔记第二十三天-成员内部类1
java202303java学习笔记第二十三天-成员内部类1
37 0