前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unsafe思考 -- 不用new构造器能创建对象么

Unsafe思考 -- 不用new构造器能创建对象么

作者头像
叫我阿柒啊
发布2022-05-09 20:21:27
7190
发布2022-05-09 20:21:27
举报
文章被收录于专栏:Java放弃之路入门到放弃之路

前言

Java中,最常用的就是通过new调用相应构造器来创建对象实例,而当构造器不是public,而是private,new没了用武之地,我们又该怎样创建对象实例?先创建构造器被private修饰的类,代码如下:

代码语言:javascript
复制
@Setter
@Getter
public class Girlfriend {
    private String name;
    private int age;
    private Girlfriend(String name) {
        this.name = name;
    }
}

让我最后再new一遍......

反射创建实例

类加载触发时机曾云:除了new,我还有反射。通过反射,可以获取类的字段、方法,同样可以获取类的构造器来创建对象。对于private修饰的字段、方法、类构造器,我们必须禁用安全检查才能获取到。

未禁止安全检查

默认是开启安全检查机制,对于被private修饰的方法、类、构造器会禁止访问。

运行结果:

禁止安全检查

通过setAccessible(true)来关闭安全检查,访问private修饰的构造器。

运行结果:

就这,就这?其实这篇文章真的想讲的是Unsafe,一个可以直接操作内存,不用构造器就可以创建对象的类。

sun.mics.Unsafe

jvm的出现,让Java不再有C语言管理内存的困扰,同时也失去了类似指针操作内存的功能。于是Unsafe的出现填补了空缺。但正如其名,直接操作内存被认为是不安全的,会带来很多安全问题。所以,Unsafe没法通过new实例化,唯一一个构造器也是private。我们查看一下源码中和Unsafe实例有关的字段、方法。

源码如下:

代码语言:javascript
复制
public final class Unsafe {
    private static final Unsafe theUnsafe;
    private Unsafe() {
    }
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        // 检查调用类的加载器是不是Bootstrap,也就是null
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
 }

不难看出getUnsafe()是一个public方法,但是它会检查调用getUsafe()类的加载器是不是Bootstrap类加载器,但是我们定义类的默认加载器是AppClassLoader,所以会直接抛出异常。

检查类加载器代码如下:

代码语言:javascript
复制
 // bootstrap加载器负责加载rt.jar,不是java编写,所以是null
    public static boolean isSystemDomainLoader(ClassLoader var0) {
        return var0 == null;
    }

那么,我们只能通过反射从theUnsafe字段和构造器来创建实例。

代码如下:

代码语言:javascript
复制
public class Boy {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Class<Unsafe> unsafeClass = Unsafe.class;
        // 第一种方式:通过构造器获取Unsafe实例
        Constructor<Unsafe> declaredConstructor = unsafeClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Unsafe unsafe1 = declaredConstructor.newInstance();

        // 第二种方法:通过字段获取Unsafe实例
        Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe2 = (Unsafe)theUnsafe.get(null);
    }
}
这样用两种方法获取到了unsafe实例对象,接下来看一下如何构建实例。
allocateInstance():构建实例

利用unsafe实例,使用这个方法可以直接不通过构造器来创建实例对象。

代码语言:javascript
复制
// 利用Unsafe实例构造GirlFriends对象
  Girlfriend girlfriend1 = (Girlfriend) unsafe1.allocateInstance(Girlfriend.class);
  Girlfriend girlfriend2 = (Girlfriend) unsafe2.allocateInstance(Girlfriend.class);
  girlfriend1.setName("小芳1");
  girlfriend2.setName("小芳2");
  System.out.println(girlfriend1.getName());
  System.out.println(girlfriend2.getName());

运行结果:

从运行结果可以看出来,两种方式获取的unsafe对象都能用来创建实例对象。那么Unsafe还有什么其他的功能。现在Unsafe一共提供了100多个方法,现在就找几个来测试一下。

putObject():修改对象成员变量
代码语言:javascript
复制
 Field nameField = Girlfriend.class.getDeclaredField("name");
 System.out.println(girlfriend1.getName());
 // 获取name成员变量在内存中的地址相对于对象内存地址的偏移量
 long l = unsafe1.objectFieldOffset(nameField);
 // 修改成员变量name的值
 unsafe1.putObject(girlfriend1, l, "小红");
 System.out.println(girlfriend1.getName());

运行结果:

compareAndSwapInt():原子修改int属性值

有兴趣的可以了解一下锁里面的CAS原理

代码语言:javascript
复制
Field ageField = Girlfriend.class.getDeclaredField("age");
long l1 = unsafe1.objectFieldOffset(ageField);
girlfriend1.setAge(20);
System.out.println("初始年龄: " + girlfriend1.getAge());
// cas操作,第三个参数必须是旧值,即20,否则修改失败。
unsafe1.compareAndSwapInt(girlfriend1, l1, 20, 18);
System.out.println("修改后年龄: " +girlfriend1.getAge());
// 通过偏移量获取int值
int i = unsafe1.getInt(girlfriend1, l1);
System.out.println("偏移量获取int值:" + i);

运行结果:

allocateMemory():分配内存
代码语言:javascript
复制
// 分配一个8byte的内存,并返回入口地址
long address = unsafe1.allocateMemory(8L);
// 用0000 0000代表一个字节,从入口地址初始化8个字节
unsafe1.setMemory(address, 8L, (byte) 0);
// aLong为0
long aLong1 = unsafe1.getLong(address);
// 修改以address为入口的long类型数据(8byte)
unsafe1.putLong(address,100L);
// aLong2为100
long aLong2 = unsafe1.getLong(address);

结语

Unsafe还要其他的方法,有兴趣可以探究一下。其核心方法主要是围绕着内存地址入口、地址偏移量来展开,在开发中还请慎用。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 入门到放弃之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 反射创建实例
    • 未禁止安全检查
      • 禁止安全检查
      • sun.mics.Unsafe
        • 这样用两种方法获取到了unsafe实例对象,接下来看一下如何构建实例。
          • allocateInstance():构建实例
            • putObject():修改对象成员变量
              • compareAndSwapInt():原子修改int属性值
                • allocateMemory():分配内存
                • 结语
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档