0%

Unsafe类解析

Unsafe类解析

概述

  • Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用
  • 但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。这也是Unsafe类得名的由来

Unsafe类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static final Unsafe theUnsafe;
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
theUnsafe = new Unsafe();
ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
ADDRESS_SIZE = theUnsafe.addressSize();
}

private Unsafe() {
}

@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
  • 从源码可以看出Unsafe类是单例模式的,提供静态方法getUnsafe获取Unsafe实例,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常,换句话说就是用户的自定义类不允许直接使用此不安全的类

  • 如果确实想在自定义类中使用Unsafe类有以下两个方法

    • getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。

      1
      java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径 
    • 通过反射获取单例对象theUnsafe。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      private static Unsafe reflectGetUnsafe() {
      try {
      Field field = Unsafe.class.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      // 静态属性
      return (Unsafe) field.get(null);
      } catch (Exception e) {
      log.error(e.getMessage(), e);
      return null;
      }
      }

Unsafe类的功能概览

image-20210522160908276

内存操作(主要是对堆外内存的操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar,getByte,getShort,getFloat等
public native Object getObject(Object o, long offset);
//为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);

// 注意下述的两个函数与前边的同名重载函数的区别,前者是从某对象的某偏移位置获取特定类型的值,后者是直接从给定的地址中获取对应的类型的值,但是此地址必须是allocateMemory分配的才行
//获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)类似的也有getShort、getChar等等
public native byte getByte(long address);
//为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)类似的也有putShort、putChar等等
public native void putByte(long address, byte x);
  • 通常,我们在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法
  • 使用堆外内存的原因
    • 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响
    • 提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存
典型应用
  • 下边的解释设计到了四种引用类型的原理,未来需要拆分出来,单独作为一篇文章

  • DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    protected static final Unsafe unsafe = Bits.unsafe();

    DirectByteBuffer(int cap) { // package-private

    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
    // 分配内存,返回基地址
    base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
    Bits.unreserveMemory(size, cap);
    throw x;
    }
    // 内存初始化
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
    // Round up to page boundary
    address = base + ps - (base & (ps - 1));
    } else {
    address = base;
    }
    // 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起同步被释放
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
    }
    • 如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放的?—-虚引用的典型应用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      226
      227
      228
      229
      230
      231
      232
      233
      234
      235
      236
      237
      238
      239
      240
      241
      242
      243
      244
      245
      246
      247
      248
      249
      250
      251
      252
      253
      254
      255
      256
      257
      258
      259
      260
      261
      262
      263
      264
      265
      266
      267
      268
      269
      270
      271
      272
      273
      274
      275
      276
      277
      278
      279
      280
      281
      282
      283
      284
      285
      286
      287
      288
      289
      290
      291
      292
      293
      294
      295
      296
      297
      298
      299
      300
      301
      302
      303
      304
      305
      306
      307
      308
      // Cleaner继承虚引用
      public class Cleaner extends PhantomReference<Object> {

      // 虚引用必须结合虚引用队列使用
      private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
      // ....

      // DirectByteBuffer类初始化Cleaner类的语句
      cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

      // Cleaner类提供的初始化方法
      public static Cleaner create(Object var0, Runnable var1) {
      return var1 == null ? null : add(new Cleaner(var0, var1));
      }

      // var1是DirectByteBuffer的引用,var2是Deallocator实例
      private Cleaner(Object var1, Runnable var2) {
      // 传递给虚引用的构造函数,至此,DirectByteBuffer拥有两个类型的引用,一个是调用DirectByteBuffer类的上层类中持有的强引用,一个是Cleaner中持有(本质上是被其父类的父类Reference持有)的虚引用
      super(var1, dummyQueue);
      this.thunk = var2;
      }

      // Cleaner内部维护了一个简单的链表
      private static synchronized Cleaner add(Cleaner var0) {
      if (first != null) {
      var0.next = first;
      first.prev = var0;
      }

      first = var0;
      return var0;
      }


      public void clean() {
      if (remove(this)) {
      try {
      this.thunk.run();
      } catch (final Throwable var2) {
      AccessController.doPrivileged(new PrivilegedAction<Void>() {
      public Void run() {
      if (System.err != null) {
      (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
      }

      System.exit(1);
      return null;
      }
      });
      }

      }
      }

      // ...

      }



      // 执行堆外内存的回收的线程任务
      private static class Deallocator
      implements Runnable
      {

      private static Unsafe unsafe = Unsafe.getUnsafe();

      private long address;
      private long size;
      private int capacity;

      private Deallocator(long address, long size, int capacity) {
      assert (address != 0);
      this.address = address;
      this.size = size;
      this.capacity = capacity;
      }

      // 在clean方法中被调用
      public void run() {
      if (address == 0) {
      // Paranoia
      return;
      }
      // 释放内存
      unsafe.freeMemory(address);
      address = 0;
      Bits.unreserveMemory(size, capacity);
      }

      }



      // Reference类



      static {
      ThreadGroup tg = Thread.currentThread().getThreadGroup();
      for (ThreadGroup tgn = tg;
      tgn != null;
      tg = tgn, tgn = tg.getParent());

      // 创建高优先级的Reference Handler线程
      Thread handler = new ReferenceHandler(tg, "Reference Handler");
      /* If there were a special system-only priority greater than
      * MAX_PRIORITY, it would be used here
      */

      // 最高优先级
      handler.setPriority(Thread.MAX_PRIORITY);
      // 守护进线程
      handler.setDaemon(true);
      // 线程启动
      handler.start();

      // provide access in SharedSecrets
      SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
      @Override
      public boolean tryHandlePendingReference() {
      return tryHandlePending(false);
      }
      });
      }



      /* High-priority thread to enqueue pending References 高优先级的守护线程用来处理GC向pending列表中投入的已经不可达的引用
      */
      private static class ReferenceHandler extends Thread {

      private static void ensureClassInitialized(Class<?> clazz) {
      try {
      Class.forName(clazz.getName(), true, clazz.getClassLoader());
      } catch (ClassNotFoundException e) {
      throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
      }
      }

      static {
      // pre-load and initialize InterruptedException and Cleaner classes
      // so that we don't get into trouble later in the run loop if there's
      // memory shortage while loading/initializing them lazily.
      ensureClassInitialized(InterruptedException.class);
      ensureClassInitialized(Cleaner.class);
      }

      ReferenceHandler(ThreadGroup g, String name) {
      super(g, name);
      }

      public void run() {
      while (true) {
      tryHandlePending(true);
      }
      }
      }

      // 此属性承载着pending list中的下一个pending的reference,由JVM维护
      private static Reference<Object> pending = null;


      // 高优先级的引用处理线程会死循环的执行此方法
      static boolean tryHandlePending(boolean waitForNotify) {
      Reference<Object> r;
      Cleaner c;
      try {
      synchronized (lock) {
      if (pending != null) {
      r = pending;
      // 'instanceof' might throw OutOfMemoryError sometimes
      // so do this before un-linking 'r' from the 'pending' chain...
      c = r instanceof Cleaner ? (Cleaner) r : null;
      // unlink 'r' from 'pending' chain
      pending = r.discovered;
      r.discovered = null;
      } else {
      // The waiting on the lock may cause an OutOfMemoryError
      // because it may try to allocate exception objects.
      if (waitForNotify) {
      // 等待pending list有reference后被JVM唤醒
      lock.wait();
      }
      // retry if waited
      return waitForNotify;
      }
      }
      } catch (OutOfMemoryError x) {
      // Give other threads CPU time so they hopefully drop some live references
      // and GC reclaims some space.
      // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
      // persistently throws OOME for some time...
      Thread.yield();
      // retry
      return true;
      } catch (InterruptedException x) {
      // retry
      return true;
      }

      // Fast path for cleaners
      if (c != null) {
      // 如果当前处理的reference是Cleaner类型的则执行clean方法
      c.clean();
      return true;
      }

      // 如果当前处理的reference不是Cleaner类型则加入到queue中
      ReferenceQueue<? super Object> q = r.queue;
      if (q != ReferenceQueue.NULL) q.enqueue(r);
      return true;
      }


      Reference(T referent, ReferenceQueue<? super T> queue) {
      this.referent = referent;
      this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
      }



      // 下边补充软引用与弱引用的实现,不详细分析其原理,仅仅做未来深入理解的准备
      public class SoftReference<T> extends Reference<T> {

      /**
      * Timestamp clock, updated by the garbage collector
      由GC去更新时钟
      */
      static private long clock;

      /**
      * Timestamp updated by each invocation of the get method. The VM may use
      * this field when selecting soft references to be cleared, but it is not
      * required to do so.
      维护一个时间戳,类似于lru,每次调用get方法都会更新此时间戳,JVM可以以这个时间戳为凭证,判断内存不够时选择性的回收那些不常使用的弱引用
      */
      private long timestamp;

      /**
      * Creates a new soft reference that refers to the given object. The new
      * reference is not registered with any queue.
      *
      * @param referent object the new soft reference will refer to
      */
      public SoftReference(T referent) {
      super(referent);
      this.timestamp = clock;
      }

      /**
      * Creates a new soft reference that refers to the given object and is
      * registered with the given queue.
      *
      * @param referent object the new soft reference will refer to
      * @param q the queue with which the reference is to be registered,
      * or <tt>null</tt> if registration is not required
      *
      */
      public SoftReference(T referent, ReferenceQueue<? super T> q) {
      super(referent, q);
      this.timestamp = clock;
      }

      /**
      * Returns this reference object's referent. If this reference object has
      * been cleared, either by the program or by the garbage collector, then
      * this method returns <code>null</code>.
      *
      * @return The object to which this reference refers, or
      * <code>null</code> if this reference object has been cleared
      */
      public T get() {
      T o = super.get();
      if (o != null && this.timestamp != clock)
      this.timestamp = clock;
      return o;
      }

      }

      // 弱引用
      public class WeakReference<T> extends Reference<T> {

      /**
      * Creates a new weak reference that refers to the given object. The new
      * reference is not registered with any queue.
      *
      * @param referent object the new weak reference will refer to
      */
      public WeakReference(T referent) {
      super(referent);
      }

      /**
      * Creates a new weak reference that refers to the given object and is
      * registered with the given queue.
      *
      * @param referent object the new weak reference will refer to
      * @param q the queue with which the reference is to be registered,
      * or <tt>null</tt> if registration is not required
      */
      public WeakReference(T referent, ReferenceQueue<? super T> q) {
      super(referent, q);
      }

      }

      • Cleaner继承自Java四大引用类型之一的虚引用PhantomReference(众所周知,无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生GC的时候,其均可被回收),PhantomReference必须与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能
      • 当某个被Cleaner引用的对象(DirectByteBuffer)将被回收时(DirectByteBuffer的强引用已经无效,只剩下虚引用,这个虚引用的类型其实就是Cleaner(继承自PhantomReference)),JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表(不是dummyQueue,pending列表是GC或者说是JVM维护的,pending列表中的非Cleaner的引用才会放入到对应的引用队列中)中,等待Reference-Handler进行相关处理。其中,Reference-Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作

      image-20210522172947851

      • DirectByteBuffer的唯一强引用失效后,其作为不可达对象剩下的唯一引用就是其对应的Cleaner类型的虚引用
    • Reference代码中包含的逻辑并非是四种引用的特性生效的全部逻辑,是需要JVM与GC配合使用的(逻辑不可见,只能从注释中瞥见一二),包括四种引用类型的子类的代码中也有对应的逻辑,大概的逻辑应该可以用下图去理解

CAS相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);


public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

return var6;
}

public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));

return var5;
}

public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var4));

return var6;
}

public final Object getAndSetObject(Object var1, long var2, Object var4) {
Object var5;
do {
var5 = this.getObjectVolatile(var1, var2);
} while(!this.compareAndSwapObject(var1, var2, var5, var4));

return var5;
}
  • Unsafe提供了三个native的CAS方法,以及几个内部封装的getAndSet方法,其区别在于以下几点
    • getAndSet方法提供了CAS自旋,而native方法是一次性执行的
    • native方法参数中的“期望值”是调用方直接传入的,而getAndSet方法中的“期望值”是使用另外一组Unsafe native方法提供的即对象相关的native方法(getIntVolatile等等)
  • AtomicIntegerConcurrentHashMapFutureTask以及AQS中都用到了Unsafe提供的CAS实现(几乎所有并发编程的类中都使用了CAS),对于AtomicInteger而言其封装方法中,既有对三个CAS native方法的直接调用,也有对Unsafe内部的getAndSet方法的调用

线程调度

1
2
3
4
5
// 线程恢复
public native void unpark(Object var1);

// 线程挂起
public native void park(boolean var1, long var2);
  • 关于Unsafe中线程调度的典型应用就是LockSupport类,AQS以及ConcurrentHashMapSychronousQueueFutureTask中实现线层挂起与唤醒使用的就是LockSupport.park()LockSupport.unpark(),而LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现

Class相关

1
2
3
4
5
6
7
8
9
10
11
12
// 获取给定静态字段的内存偏移地址,此值对于给定的字段是唯一且固定不变的
public native long staticFieldOffset(Field var1);
// 获取给定静态字段存储的对象指针
public native Object staticFieldBase(Field var1);
// 判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。
public native boolean shouldBeInitialized(Class<?> var1);
//检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用
public native void ensureClassInitialized(Class<?> var1);
//定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
//定义一个匿名类
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
  • 从Java 8开始,JDK使用invokedynamic及VM Anonymous Class结合来实现Java语言层面上的Lambda表达式。

    • invokedynamic: invokedynamic是Java 7为了实现在JVM上运行动态语言而引入的一条新的虚拟机指令,它可以实现在运行期动态解析出调用点限定符所引用的方法,然后再执行该方法,invokedynamic指令的分派逻辑是由用户设定的引导方法决定

    • VM Anonymous Class可以看做是一种模板机制,针对于程序动态生成很多结构相同、仅若干常量不同的类时,可以先创建包含常量占位符的模板类,而后通过Unsafe.defineAnonymousClass方法定义具体类时填充模板的占位符生成具体的匿名类。生成的匿名类不显式挂在任何ClassLoader下面,只要当该类没有存在的实例对象、且没有强引用来引用该类的Class对象时,该类就会被GC回收故而VM Anonymous Class相比于Java语言层面的匿名内部类无需通过ClassClassLoader进行类加载且更易回收

    • 在Lambda表达式实现中,通过invokedynamic指令调用引导方法生成调用点,在此过程中,会通过ASM动态生成字节码,而后利用Unsafe的defineAnonymousClass方法定义实现相应的函数式接口的匿名类,然后再实例化此匿名类,并返回与此匿名类中函数式方法的方法句柄关联的调用点;而后可以通过此调用点实现调用相应Lambda表达式定义逻辑的功能

对象相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
public native long objectFieldOffset(Field var1);

// 以下两个方法与内存操作中的两个同名方法是同一个

//获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);


//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义,有其他类似操作
public native Object getObjectVolatile(Object o, long offset);
//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义,有其他类似操作
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效,有其他类似操作
public native void putOrderedObject(Object o, long offset, Object x);
//绕过构造方法、初始化代码来创建对象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
  • 所谓的以Volatile的语义加载,意思应该就是获取该被volatile修饰的字段的最新的值
  • 关于allocateInsatnce
    • 常规对象实例化方式:我们通常所用到的创建对象的方式,从本质上来讲,都是通过new机制来实现对象的创建。但是,new机制有个特点就是当类只提供有参的构造函数且无显示声明无参构造函数时,则必须使用有参构造函数进行对象构造,而使用有参构造函数时,必须传递相应个数的参数才能完成对象实例化。
    • 非常规的实例化方式:而Unsafe中提供allocateInstance方法,仅通过Class对象就可以创建此类的实例对象,而且不需要调用其构造函数、初始化代码、JVM安全检查等。它抑制修饰符检测,也就是即使构造器是private修饰的也能通过此方法实例化,只需提类对象即可创建相应的对象。由于这种特性,allocateInstance在java.lang.invoke、Objenesis(提供绕过类构造器的对象生成方式)、Gson(反序列化时用到)中都有相应的应用
      • 在Gson反序列化时,如果类有默认构造函数,则通过反射调用默认构造函数创建实例,否则通过UnsafeAllocator来实现对象实例的构造,UnsafeAllocator通过调用Unsafe的allocateInstance实现对象的实例化,保证在目标类无默认构造函数时,反序列化不够影响

数组相关

1
//返回数组中第一个元素的偏移地址public native int arrayBaseOffset(Class<?> var1);//返回数组中一个元素占用的大小public native int arrayIndexScale(Class<?> var1);
  • 实际上获取一个数组实例的以上两个要素后,就能对数组中任一个索引的内存地址进行定位,最典型的应用就是Atomic的数组类中,比如AtomicIntegerArray

    1
    // AtomicIntegerArray 类private static final int base = unsafe.arrayBaseOffset(int[].class);static {  int scale = unsafe.arrayIndexScale(int[].class);  if ((scale & (scale - 1)) != 0)    throw new Error("data type scale not a power of two");  shift = 31 - Integer.numberOfLeadingZeros(scale);}private long checkedByteOffset(int i) {  if (i < 0 || i >= array.length)    throw new IndexOutOfBoundsException("index " + i);  return byteOffset(i);}// 给定索引返回该索引对应的内存地址private static long byteOffset(int i) {  return ((long) i << shift) + base;}
    • 关于数组相关的部分,需要注意的是计算的都是偏移地址

内存屏障

1
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前public native void loadFence();//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前public native void storeFence();//内存屏障,禁止load、store操作重排序public native void fullFence();
  • 在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序

  • 同样的概念也出现在synchronizedvolatile两个关键字得以实现可见性以及有序性的原理中

  • 在Java 8中引入的读写锁的实现——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现(写锁仍然是独占的),这种乐观读锁类似于无锁的操作,完全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题(这种不一致的场景还是比较容易想出来的:读线程加载数据到自己的工作内存以进行计算,但是随后写线程同样加载数据,并对数据进行修改,此时则出现了数据不一致,虽然都是可见性问题,但是这里的问题是代码块锁的问题不是volatile这样的针对一个变量的问题,不要混淆(换句话说,下边的代码可能把x,y用volatile修饰可以解决部分问题,但是限制会比较多–volatile仅仅保证一个变量的读写指令的可见性)),所以当使用StampedLock的乐观读锁时,需要遵从如下图用例中使用的模式来确保数据的一致性

    image-20210524190429484

    • 如上图用例所示计算坐标点Point对象,包含点移动方法move及计算此点到原点的距离的方法distanceFromOrigin。在方法distanceFromOrigin中,首先,通过tryOptimisticRead方法获取乐观读标记;然后从主内存中加载点的坐标值 (x,y);而后通过StampedLock的validate方法校验锁状态,判断坐标点(x,y)从主内存加载到线程工作内存过程中(那么判断完毕之后的过程中,如果出现写线程更新怎么办,总不能一直判断吧,解释在下边),主内存的值是否已被其他线程通过move方法修改,如果validate返回值为true,证明(x, y)的值未被修改,可参与后续计算;否则,需加悲观读锁(获取悲观读锁后,不允许写线程再获取锁),再次从主内存加载(x,y)的最新值,然后再进行距离计算。其中,校验锁状态这步操作至关重要,需要判断锁状态是否发生改变,从而判断之前copy到线程工作内存中的值是否与主内存的值存在不一致

      • 这里就忽视了锁的使命是什么了,锁的作用是为了保证并发过程中逻辑的完整性(原子性),不要在数据处理过程中,将旧的数据与新的数据混淆计算,造成状态的混乱;保证从 tryOptimisticReadvalidate 之间的代码执行的逻辑完整性(比如不会出现这样的状况,读了x后,对应的y状态被更新,此时x与y就不是匹配的一对坐标),不保证 validate 之后的写入,那个跟你这次读已经无关了
    • 在validate方法中,通过锁标记与相关常量进行位运算、比较来校验锁状态,在校验逻辑之前,会通过Unsafe的loadFence方法加入一个load内存屏障,目的是避免上图用例中步骤②和StampedLock.validate中锁状态校验运算发生重排序导致锁状态校验不准确的问题

      1
      2
      3
      4
      5
      public boolean validate(long stamp) {
      // load内存屏障,避免屏障前边的读操作与后边的读操作重排序,其实理解还是不深
      U.loadFence();
      return (stamp & SBITS) == (state & SBITS);
      }

系统相关

1
2
3
4
//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();
  • java.nio下的工具类Bits中计算待申请内存所需内存页数量的静态方法,其依赖于Unsafe中pageSize方法获取系统内存页大小实现后续计算逻辑(前边说的DirectByteBuffer也使用了此方法)

    1
    2
    3
    4
    5
    static int pageSize() {
    if (pageSize == -1)
    pageSize = unsafe().pageSize();
    return pageSize;
    }

参考

  1. Java魔法类:Unsafe应用解析