美文网首页
Java中Unsafe类详解

Java中Unsafe类详解

作者: 技术灭霸 | 来源:发表于2020-04-09 03:53 被阅读0次

本文主要介绍一下 JDK 里的核弹之 sun.misc.Unsafe 类。

0、概述

本文主要介绍一下 JDK 里的核弹之 sun.misc.Unsafe 类。这个类提供了更加底层的操作,主要是服务于 Java 的核心类库。普通用户一般情况下,并不会直接用到该类。

1、 获取 Unsafe 实例

Unsafe 提供了 getUnsafe 方法,代码实例如下所示:

public class Main {
 public static void main(String[] args) {
     Unsafe.getUnsafe();
 }
}

但是,会有如下报错:

Exception in thread "main" java.lang.SecurityException: Unsafe
 at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
 at com.bendcap.java.jvm.unsafe.Main.main(Main.java:13)

这是因为 Unsafe 类主要是 JDK 内部使用,并不提供给普通用户调用,也就是其名字所暗示的那样,这些操作不安全。

但是我们仍让可以通过反射获取到实例:

public static Unsafe createUnsafe() {
    try {
        Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
        Field field = unsafeClass.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        return unsafe;
       } catch (Exception e) {
        e.printStackTrace();
    }
 return null;
 }

2、 不调用构造方法方法创建对象

我们知道,JVM 在创建对象实例的时候会调用默认或者有参构造方法,在字节码中对应 init 方法,那如何实例话一个对象,而不调用 init 方法呢? 答案就是 Unsafe

假设我们有一个类 Person:

class Person {
 private String name;
 private int age;
 .....
}

那么我们可以通过 Unsafe 在内存中直接创建 Person 的实例:

public Person instancePerson() {
        Person person = null;
        try {
            person = (Person) createUnsafe().allocateInstance(Person.class);
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        person.setName("Unsafer");
        return person;
    }

Unsafe 只会分配 Person 对应的内存空间,而不触发构造函数。
: Class 文件中的 clinit 仍然会执行。

举一个生产实例。Gson 中反序列化的时候,ReflectiveTypeAdapterFactory 类负责通过反射设置字段值,其中在或许反序列化对应的 Class 实例的时候就用到了 Unsafe, 关键代码摘抄如下:

public abstract class UnsafeAllocator {
  public abstract <T> T newInstance(Class<T> c) throws Exception;

  public static UnsafeAllocator create() {
    // try JVM
    // public class Unsafe {
    //   public Object allocateInstance(Class<?> type);
    // }
    try {
      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
      Field f = unsafeClass.getDeclaredField("theUnsafe");
      f.setAccessible(true);
      final Object unsafe = f.get(null);
      final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
      return new UnsafeAllocator() {
        @Override
        @SuppressWarnings("unchecked")
        public <T> T newInstance(Class<T> c) throws Exception {
          assertInstantiable(c);
          return (T) allocateInstance.invoke(unsafe, c);
        }
      };
    } catch (Exception ignored) {
    }
    ...
    // give up
    return new UnsafeAllocator() {
      @Override
      public <T> T newInstance(Class<T> c) {
        throw new UnsupportedOperationException("Cannot allocate " + c);
      }
    };
  }
}

3、改变私有字段

假设我们有如下类:

public class SecretHolder {
    private int SECRET_VALUE = 0;

    public boolean secretValueDisclosed() {
        return SECRET_VALUE == 1;
    }
}

下面我们通过 Unsafe 来改变私有属性 SECRET_VALUE 的值。

       SecretHolder secretHolder = new SecretHolder();
       Field field = null;
       try {
           field = secretHolder.getClass().getDeclaredField("SECRET_VALUE");
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }
       Unsafe unsafe = createUnsafe();
       unsafe.putInt(secretHolder, unsafe.objectFieldOffset(field), 1);
       return secretHolder.secretValueDisclosed();
}

我们通过 unsafe.putInt 直接改变了 SecretHolder 的私有属性的值。一旦我们通过反射获得了类的私有属性字段,我们就可以直接操作它的值。

4、 抛出异常而不会触发 CE(Checked Exception)

通过 unsafe.throwExceptio 创建的异常不会被编译器检查,方法的调用者也不需要处理异常。

public void throwException() {
       Unsafe unsafe = createUnsafe();
       unsafe.throwException(new IOException());
   }

5、 堆外内存

Java 中对象分配一般是在 Heap 中进行的(例外是 TLAB等),当应用内存不足的时候,可以通过触发 GC 进行垃圾回收,但是如果有大量对象存活到永久代,并且仍然引用可达,那么我们就需要堆外内存(Off-Heap Memory)来缓解频繁 GC 造成的压力。

Unsafe.allocateMemory 给了我们在直接内存中分配对象的能力,这块内存是非堆内存,因此,不会受到 GC 的频繁分析和干扰。

虽然这样可以缓解大量对象占用内存对 GC 和 JVM 造成的压力,这也就需要我们手动管理内存,因此,在合适的事后我们需要手动调用 freeMemory 来释放内存。

举例,我们在内存中分配字节数组:

public class OffHeapArray {
    private final static int BYTE = 1;
    private long size;
    private long address;
    private Unsafe unsafe;

    public OffHeapArray(long size, Unsafe unsafe) {
        this.size = size;
        this.unsafe = unsafe;
        address = unsafe.allocateMemory(size * BYTE);
    }

    public void set(long i, byte value) {
        unsafe.putByte(address + i * BYTE, value);
    }

    public int get(long idx) {
        return unsafe.getByte(address + idx * BYTE);
    }

    public long size() {
        return size;
    }

    public void freeMemory() {
        unsafe.freeMemory(address);
    }
}

6、CAS(CompareAndSwap)

java.concurrent 包中提供了大量并发相关的操作,例如 AtomicInteger 就用了 Unsafe.compareAndSwap 操作来实现 lock-free 的操作,保证更好的性能。

假设我们做一个累加器,开启 1000 个线程,每个线程循环累加 10_000 次。

public class CASCounter {
    private Unsafe unsafe;
    private volatile long counter = 0;
    private long offset;

    public CASCounter(Unsafe unsafe) {
        this.unsafe = unsafe;
        try {
            offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    public void increment() {
        long before = counter;
        while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
            before = counter;
        }
    }


    public long getCounter() {
        return counter;
    }
}

counter 需要声明为 volatile 来保证对所有线程可见(参考 Java 内存模型以及指令集重排)

这里的关键是 increment 方法,我们在 while 循环里不断尝试调用 compareAndSwapLong,检查在我们方法内部累加的同事,counter 的值有没有被其他线程改变。如有没有,就提交更改,如果不一致,那么继续尝试提交更改。

7、Park/Unpark

Park/Unpark 主要是被 JVM 用来做线程的上下文切换。当线程需要等待某个条件的时候,JVM 会调用 park 来阻塞该线程。

这和 Object.await 非常类似,但是 park 是操作系统调用,因此,在某些操作系统架构上,这会带来更好的性能。

当线程阻塞后,需要再次执行, JVM 会调用 unpark 方法使得该线程变得活跃。

结论

虽然 Unsafe 看起来不会被用到,但是能帮助我们更好的理解 JVM 以及 JDK 中 lock-free 的实现。还有一点就是 Off-Heap Memory, 如果做服务端开发中确实遇到了大内存对象并且常驻内存的情况,堆外分配不失为一个好的策略来减轻 GC 以及 GC 带来的系统负担(可参见 R 大在阿里 JVM 中所做的一些优化),与之对应的就是 TLAB(thread local allocation buffer)。

相关文章

  • Java中Unsafe类详解

    本文主要介绍一下 JDK 里的核弹之 sun.misc.Unsafe 类。 0、概述 本文主要介绍一下 JDK 里...

  • 【安卓逆向】Java中的魔术类

    简单谈一谈Java中的Unsafe类 Unsafe类是啥? Java最初被设计为一种安全的受控环境。尽管如此,Ja...

  • Unsafe类详解

    Unsafe类是进行底层操作的方法集合,可以直接操作内存,进行一些非常规操作,所以说是"不安全"的操作,但是因为直...

  • Java Part 3: SecurityManager

    翻译自:securitymanager Intro 在 Java 中可以用 sun.misc.Unsafe 类为所...

  • 一起读读JAVA源码-Atomic

    AtomicBoolean AtomicInteger 解释 核心操作类Unsafe,JAVA提供工具类unsaf...

  • Unsafe 与 LockSupport

    1.Unsafe java concurrent 包的基础是CAS, 而进行CAS操作的就是这个 Unsafe类....

  • Unsafe类

    Unsafe类是Java不对外提供的不安全的类,juc包中的atomic*类都是用这个类实现的 // Uns...

  • AQS研究系列(一)--Unsafe使用

    前言 为了研究AQS,我们先来学习下java中cas(Compare And Swap)的基础Unsafe类的使用...

  • AtomicInteger源码分析

    **AtomicInteger** 是Java提供的原子操作类,其内部通过 **UnSafe** 工具类,使用 =...

  • Java中的UnSafe类学习

    基本介绍 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问...

网友评论

      本文标题:Java中Unsafe类详解

      本文链接:https://www.haomeiwen.com/subject/bonfmhtx.html