作者:rickiyang
出处:www.cnblogs.com/rickiyang/p/11334887.html
Unsafe是位于sun.misc包下的一个类,次要提供一些用于执行低级别、不平安操作的办法,如间接拜访零碎内存资源、自主治理内存资源等,这些办法在晋升Java运行效率、加强Java语言底层资源操作能力方面起到了很大的作用。
然而,这个类的作者不心愿咱们应用它,因为咱们尽管咱们获取到了对底层的控制权,然而也增大了危险,安全性正是Java绝对于C++/C的劣势。因为该类在sun.misc
包下,默认是被BootstrapClassLoader加载的。如果咱们在程序中去调用这个类的话,咱们应用的类加载器必定是 AppClassLoader,问题是在Unsafe中是这样写的:
private static final Unsafe theUnsafe;private Unsafe() {}@CallerSensitivepublic static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; }}
将构造函数公有,而后提供了一个静态方法去获取以后类实例。在getUnsafe()
办法中首先判断以后类加载器是否为空,因为应用 BootstrapClassLoader 自身就是空,它是用c++实现的,这样就限度了咱们在本人的代码中应用这个类。
然而同时作者也算是给咱们提供了一个后门,因为Java有反射机制。调用的思路就是将theUnsafe
对象设置为可见。
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) theUnsafeField.get(null);System.out.println(unsafe);
unsafe类性能介绍:
内存操作
这部分次要蕴含堆外内存的调配、拷贝、开释、给定地址值操作等办法。
//分配内存, 相当于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等public native Object getObject(Object o, long offset);//为给定地址设置值,疏忽润饰限定符的拜访限度,与此相似操作还有: putInt,putDouble,putLong,putChar等public native void putObject(Object o, long offset, Object x);//获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory调配时,此办法后果为确定的)public native byte getByte(long address);//为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory调配时,此办法后果才是确定的)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来实现。
上面的代码为DirectByteBuffer构造函数,创立DirectByteBuffer的时候,通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化,而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,调配的堆外内存一起被开释。
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; } //跟踪directbytebuffer 对象的垃圾回收,实现堆外内存的开释 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null;}
下面最初一句代码通过Cleaner.create()
来进行对象监控,开释堆外内存。这里是如何做到的呢?跟踪一下Cleaner类:
public class Cleaner extends PhantomReference<Object> { public static Cleaner create(Object var0, Runnable var1) { return var1 == null ? null : add(new Cleaner(var0, var1)); }}
能够看到继承了PhantomReference
,Java中的4大援用类型咱们都晓得。PhantomReference的作用于其余的Refenrence作用大有不同。像 SoftReference、WeakReference都是为了保障援用的类对象能在不必的时候及时的被回收,然而 PhantomReference 并不会决定对象的生命周期。如果一个对象仅持有虚援用,那么它就和没有任何援用一样,对象不可达时就会被垃圾回收器回收,然而任何时候都无奈通过虚援用取得对象。虚援用次要用来跟踪对象被垃圾回收器回收的流动。
那他的作用到底是啥呢?精确来说 PhantomReference 给使用者提供了一种机制-来监控对象的垃圾回收的流动。
可能这样说不是太明确,我来举个例子:
package com.rickiyang.learn.javaagent;import java.lang.ref.PhantomReference;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.lang.reflect.Field;/** * @author rickiyang * @date 2019-08-08 * @Desc */public class TestPhantomReference { public static boolean isRun = true; public static void main(String[] args) throws Exception { String str = new String("123"); System.out.println(str.getClass() + "@" + str.hashCode()); final ReferenceQueue<String> referenceQueue = new ReferenceQueue<>(); new Thread(() -> { while (isRun) { Object obj = referenceQueue.poll(); if (obj != null) { try { Field rereferent = Reference.class.getDeclaredField("referent"); rereferent.setAccessible(true); Object result = rereferent.get(obj); System.out.println("gc will collect:" + result.getClass() + "@" + result.hashCode() + "\t" + result); } catch (Exception e) { e.printStackTrace(); } } } }).start(); PhantomReference<String> weakRef = new PhantomReference<>(str, referenceQueue); str = null; Thread.currentThread().sleep(2000); System.gc(); Thread.currentThread().sleep(2000); isRun = false; }}
下面这段代码的含意是new PhantomReference(),因为PhantomReference必须的保护一个ReferenceQueue用来保留以后被虚援用的对象。上例中手动去调用referenceQueue.poll()
办法,这里你须要留神的是并不是咱们被动去开释queue中的对象,你跟踪进去 poll() 办法能够看到有一个全局锁对象,只有当以后对象失去了援用之后才会开释锁,poll()办法能力执行。在执行poll()办法开释对象的时候咱们能够针对这个对象做一些监控。这就是 PhantomReference 的意义所在。
说回到 Cleaner, 通过看源码,create()
办法调用了add()
办法,在Cleaner类外面保护了一个双向链表,将每一个add进来的Cleaner对象都增加到这个链表中保护。那么在Cleaner 链表中的对象切实何时被开释掉呢?
留神到 Cleaner中有一个clean()办法:
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; } }); } }}
remove()办法是将该对象从外部保护的双向链表中革除。上面紧跟着是thunk.run()
,thunk = 咱们通过create()
办法传进来的参数,在`DirectByteBuffer中
那就是:Cleaner.create(this, new Deallocator(base, size, cap))
,Deallocator类也是一个线程:
private static class Deallocator implements Runnable { private static Unsafe unsafe = Unsafe.getUnsafe(); //省略无关 代码 public void run() { if (address == 0) { // Paranoia return; } unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); } }
看到在run办法中调用了freeMemory()
去开释掉对象。
在 Reference
类中调用了该办法,Reference 类中的动态代码块 有个一外部类:ReferenceHandler
,它继承了 Thread,在run办法中调用了 tryHandlePending()
,并且被设置为守护线程,意味着会循环不断的解决pending链表中的对象援用。
这里要留神的点是:
Cleaner自身不带有清理逻辑,所有的逻辑都封装在thunk中,因而thunk是怎么实现的才是最要害的。
另外,Java 最新核心技术系列教程和示例源码看这里:https://github.com/javastacks...
static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); 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); } });}static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { synchronized (lock) { if (pending != null) { r = pending; //如果以后Reference对象是Cleaner类型的就进行非凡解决 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) { lock.wait(); } // retry if waited return waitForNotify; } } } catch (OutOfMemoryError x) { Thread.yield(); // retry return true; } catch (InterruptedException x) { // retry return true; } // clean 不为空的时候,走清理的逻辑 if (c != null) { c.clean(); return true; } ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true;}
tryHandlePending
这段代码的意思是:
如果一个对象通过JVM检测他曾经没有强援用了,然而还有 弱援用 或者 软援用 或者 虚援用的状况下,那么就会把此对象放到一个名为pending的链表里,这个链表是通过Reference.discovered域连贯在一起的。
ReferenceHandler
这个线程会始终从链表中取出被pending的对象,它可能是WeakReference,也可能是SoftReference,当然也可能是PhantomReference和Cleaner。如果是Cleaner,那就间接调用Cleaner的clean办法,而后就完结了。其余的状况下,要交给这个对象所关联的queue,以便于后续的解决。
对于堆外内存调配和回收的代码咱们就先剖析到这里。须要留神的是对外内存回收的机会也是不确定的,所以不要继续调配一些大对象到堆外,如果没有被回收掉,这是一件很可怕的事件。毕竟它无奈被JVM检测到。
内存屏障
硬件层的内存屏障分为两种:Load Barrier
和 Store Barrier
即读屏障和写屏障。内存屏障有两个作用:阻止屏障两侧的指令重排序;强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据生效。在Unsafe中提供了三个办法来操作内存屏障:
//读屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前public native void loadFence();//写屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前public native void storeFence();//全能屏障,禁止load、store操作重排序public native void fullFence();
先简略理解两个指令:
- Store:将处理器缓存的数据刷新到内存中。
- Load:将内存存储的数据拷贝到处理器的缓存中。
JVM平台提供了一下几种内存屏障:
屏障类型 | 指令示例 | 阐明 |
---|---|---|
LoadLoad Barriers | Load1;LoadLoad;Load2 | 该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作 |
StoreStore Barriers | Store1;StoreStore;Store2 | 该屏障确保Store1立即刷新数据到内存(使其对其余处理器可见)该操作先于Store2及其后所有存储指令的操作 |
LoadStore Barriers | Load1;LoadStore;Store2 | 确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作 |
StoreLoad Barriers | Store1;StoreLoad;Load2 | 该屏障确保Store1立即刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作。它会使该屏障之前的所有内存拜访指令(存储指令和拜访指令)实现之后,才执行该屏障之后的内存拜访指令 |
StoreLoad Barriers同时具备其余三个屏障的成果,因而也称之为全能屏障
(mfence),是目前大多数处理器所反对的;然而绝对其余屏障,该屏障的开销绝对低廉。
loadFence
实现了LoadLoad Barriers,该操作禁止了指令的重排序。
storeFence
实现了 StoreStore Barriers,确保屏障前的写操作可能立即刷入到主内存,并且确保屏障前的写操作肯定先于屏障后的写操作。即保障了内存可见性和禁止指令重排序。
fullFence
实现了 StoreLoad Barriers,强制所有在mfence指令之前的store/load指令,都在该mfence指令执行之前被执行;所有在mfence指令之后的store/load指令,都在该mfence指令执行之后被执行。
在 JDK 中调用了 内存屏障这几个办法的实现类有 StampedLock
。对于StampedLock
的实现咱们前面会专门抽出一篇去解说。它并没有去实现AQS队列。而是采纳了 其余形式实现。
零碎相干
这部分蕴含两个获取零碎相干信息的办法。
//返回零碎指针的大小。返回值为4(32位零碎)或 8(64位零碎)。public native int addressSize(); //内存页的大小,此值为2的幂次方。public native int pageSize();
在 java.nio
下的Bits类中调用了pagesize()办法计算零碎中页大小:
private static int pageSize = -1;static int pageSize() { if (pageSize == -1) pageSize = unsafe().pageSize(); return pageSize;}
线程调度
线程调度中提供的办法包含:线程的挂起,复原 和 对象锁机制等,其中获取对象的监视器锁办法曾经被标记为弃用。
// 终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是应用这两个办法public native void unpark(Object thread);// 线程调用该办法,线程将始终阻塞直到超时,或者是中断条件呈现。public native void park(boolean isAbsolute, long time);//取得对象锁(可重入锁)@Deprecatedpublic native void monitorEnter(Object o);//开释对象锁@Deprecatedpublic native void monitorExit(Object o);//尝试获取对象锁@Deprecatedpublic native boolean tryMonitorEnter(Object o);
将一个线程进行挂起是通过 park 办法实现的,调用park()
后,线程将始终 阻塞 直到 超时 或者 中断 等条件呈现。unpark
能够开释一个被挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在LockSupport
类中,LockSupport 类中有各种版本 pack 办法,但最终都调用了Unsafe.park()
办法。 咱们来看一个例子:
package leetcode;import sun.misc.Unsafe;import java.lang.reflect.Field;import java.util.concurrent.TimeUnit;/** * @author: rickiyang * @date: 2019/8/10 * @description: */public class TestUsafe { private static Thread mainThread; public Unsafe getUnsafe() throws Exception { Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); return (Unsafe) theUnsafeField.get(null); } public void testPark() throws Exception { Unsafe unsafe = getUnsafe(); mainThread = Thread.currentThread(); System.out.println(String.format("park %s", mainThread.getName())); unsafe.park(false, TimeUnit.SECONDS.toNanos(3)); new Thread(() -> { System.out.println(String.format("%s unpark %s", Thread.currentThread().getName(), mainThread.getName())); unsafe.unpark(mainThread); }).start(); System.out.println("main thread is done"); } public static void main(String[] args) throws Exception { TestUsafe testUsafe = new TestUsafe(); testUsafe.testPark(); }}
运行下面的例子,那你会发现在第29行 park
办法设置了超时工夫为3秒后,会阻塞以后主线程,直到超时工夫达到,上面的代码才会继续执行。
对象操作
Unsafe类中提供了多个办法来进行 对象实例化 和 获取对象的偏移地址 的操作:
// 传入一个Class对象并创立该实例对象,但不会调用构造方法public native Object allocateInstance(Class<?> cls) throws InstantiationException;// 获取字段f在实例对象中的偏移量public native long objectFieldOffset(Field f);// 返回值就是f.getDeclaringClass()public native Object staticFieldBase(Field f);// 动态属性的偏移量,用于在对应的Class对象中读写动态属性public native long staticFieldOffset(Field f);// 取得给定对象偏移量上的int值,所谓的偏移量能够简略了解为指针指向该变量;的内存地址,// 通过偏移量便可失去该对象的变量,进行各种操作public native int getInt(Object o, long offset);// 设置给定对象上偏移量的int值public native void putInt(Object o, long offset, int x);// 取得给定对象偏移量上的援用类型的值public native Object getObject(Object o, long offset);// 设置给定对象偏移量上的援用类型的值public native void putObject(Object o, long offset, Object x););// 设置给定对象的int值,应用volatile语义,即设置后立马更新到内存对其余线程可见public native void putIntVolatile(Object o, long offset, int x);// 取得给定对象的指定偏移量offset的int值,应用volatile语义,总能获取到最新的int值。public native int getIntVolatile(Object o, long offset);// 与putIntVolatile一样,但要求被操作字段必须有volatile润饰public native void putOrderedInt(Object o, long offset, int x);
allocateInstance
办法在这几个场景下很有用:跳过对象的实例化阶段(通过构造函数)、疏忽构造函数的安全检查(反射newInstance()时)、你须要某类的实例但该类没有public的构造函数。
另外,Java 最新核心技术系列教程和示例源码看这里:https://github.com/javastacks...
举个例子:
public class User { private String name; private int age; private static String address = "beijing"; public User(){ name = "xiaoming"; } public String getname(){ return name; }} /** * 实例化对象 * @throws Exception */public void newInstance() throws Exception{ TestUsafe testUsafe = new TestUsafe(); Unsafe unsafe = testUsafe.getUnsafe(); User user = new User(); System.out.println(user.getname()); User user1 = User.class.newInstance(); System.out.println(user1.getname()); User o = (User)unsafe.allocateInstance(User.class); System.out.println(o.getname());}
打印的后果能够看到最初输入的是null,阐明构造函数未被加载。能够进一步试验,将User类中的构造函数设置为 private,你会发现在后面两种实例化形式查看期就报错。然而第三种是能够用的。这是因为allocateInstance
只是给对象调配了内存,它并不会初始化对象中的属性。
上面是对象操作的应用示例:
public void testObject() throws Exception{ TestUsafe testUsafe = new TestUsafe(); Unsafe unsafe = testUsafe.getUnsafe(); //通过allocateInstance创建对象,为其调配内存地址,不会加载构造函数 User user = (User) unsafe.allocateInstance(User.class); System.out.println(user); // Class && Field Class<? extends User> userClass = user.getClass(); Field name = userClass.getDeclaredField("name"); Field age = userClass.getDeclaredField("age"); Field location = userClass.getDeclaredField("address"); // 获取实例域name和age在对象内存中的偏移量并设置值 System.out.println(unsafe.objectFieldOffset(name)); unsafe.putObject(user, unsafe.objectFieldOffset(name), "xiaoming"); System.out.println(unsafe.objectFieldOffset(age)); unsafe.putInt(user, unsafe.objectFieldOffset(age), 18); System.out.println(user); // 获取定义location字段的类 Object staticFieldBase = unsafe.staticFieldBase(location); System.out.println(staticFieldBase); // 获取static变量address的偏移量 long staticFieldOffset = unsafe.staticFieldOffset(location); // 获取static变量address的值 System.out.println(unsafe.getObject(staticFieldBase, staticFieldOffset)); // 设置static变量address的值 unsafe.putObject(staticFieldBase, staticFieldOffset, "tianjin"); System.out.println(user + " " + user.getAddress());}
对象实例布局与内存大小
一个Java对象占用多大的内存空间呢?这个问题很值得读者敌人去查一下。 因为这个输入本篇的重点所以简略说一下。一个 Java 对象在内存中由对象头、示例数据和对齐填充形成。对象头存储了对象运行时的根本数据,如 hashCode、锁状态、GC 分代年龄、类型指针等等。实例数据是对象中的非动态字段值,可能是一个原始类型的值,也可能是一个指向其余对象的指针。对齐填充就是 padding,保障对象都采纳 8 字节对齐。除此以外,在 64 位虚拟机中还可能会开启指针压缩,将 8 字节的指针压缩为 4 字节,这里就不再过多介绍了。
也就是说一个 Java 对象在内存中,首先是对象头,而后是各个类中字段的排列,这之间可能会有 padding 填充。这样咱们大略就能了解字段偏移量的含意了,它理论就是每个字段在内存中所处的地位。
public class User { private String name; private int age;}TestUsafe testUsafe = new TestUsafe();Unsafe unsafe = testUsafe.getUnsafe();for (Field field : User.class.getDeclaredFields()) { System.out.println(field.getName() + "-" + field.getType() + ": " + unsafe.objectFieldOffset(field));}后果:name-class java.lang.String: 16age-int: 12
从下面的运行后果中能够:
age:偏移值为12,即后面 12 个字节的对象头;
name:name从16字节开始,因为int 类型的age占了4个字节。
持续算下去整个对象占用的空间,对象头12,age 4,name 是指针类型,开启指针压缩占用4个字节,那么User对象整个占用20字节,因为下面说的padding填充,必须8字节对齐,那么实际上会补上4个字节的填充,即一共占用了24个字节。
依照这种计算形式,咱们能够字节写一个计算size的工具类:
public static long sizeOf(Object o) throws Exception{ TestUsafe testUsafe = new TestUsafe(); Unsafe unsafe = testUsafe.getUnsafe(); HashSet<Field> fields = new HashSet<Field>(); Class c = o.getClass(); while (c != Object.class) { for (Field f : c.getDeclaredFields()) { if ((f.getModifiers() & Modifier.STATIC) == 0) { fields.add(f); } } //如果有继承父类的话,父类中的属性也是要计算的 c = c.getSuperclass(); } //计算每个字段的偏移量,因为第一个字段的偏移量即在对象头的根底上偏移的 //所以只须要比拟以后偏移量最大的字段即示意这是该对象最初一个字段的地位 long maxSize = 0; for (Field f : fields) { long offset = unsafe.objectFieldOffset(f); if (offset > maxSize) { maxSize = offset; } } //下面计算的是对象最初一个字段的偏移量起始地位,java中对象最大长度是8个字节(long) //这里的计算形式是 将 以后偏移量 / 8 + 8字节 的padding return ((maxSize/8) + 1) * 8;}
下面的工具类计算的后果也是24。
class相干操作
//动态属性的偏移量,用于在对应的Class对象中读写动态属性public native long staticFieldOffset(Field f);//获取一个动态字段的对象指针public native Object staticFieldBase(Field f);//判断是否须要初始化一个类,通常在获取一个类的动态属性的时候(因为一个类如果没初始化,它的动态属性也不会初始化)应用。 当且仅当ensureClassInitialized办法不失效时返回falsepublic native boolean shouldBeInitialized(Class<?> c);//确保类被初始化public native void ensureClassInitialized(Class<?> c);//定义一个类,可用于动态创建类,此办法会跳过JVM的所有安全检查,默认状况下,ClassLoader(类加载器)和ProtectionDomain(爱护域)实例来源于调用者public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);//定义一个匿名类,可用于动态创建类public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
数组操作
数组操作次要有两个办法:
//返回数组中第一个元素的偏移地址public native int arrayBaseOffset(Class<?> arrayClass);//返回数组中一个元素占用的大小public native int arrayIndexScale(Class<?> arrayClass);
CAS操作
置信所有的开发者对这个词都不生疏,在AQS类中应用了无锁的形式来进行并发管制,次要就是CAS的功绩。
CAS的全称是Compare And Swap 即比拟替换,其算法核心思想如下
执行函数:CAS(V,E,N)
蕴含3个参数
- V示意要更新的变量
- E示意预期值
- N示意新值
如果V值等于E值,则将V的值设为N。若V值和E值不同,则阐明曾经有其余线程做了更新,则以后线程什么都不做。艰深的了解就是CAS操作须要咱们提供一个期望值,当期望值与以后线程的变量值雷同时,阐明没有别的线程批改该值,以后线程能够进行批改,也就是执行CAS操作,但如果期望值与以后线程不符,则阐明该值已被其余线程批改,此时不执行更新操作,但能够抉择从新读取该变量再尝试再次批改该变量,也能够放弃操作。
Unsafe类中提供了三个办法来进行CAS操作:
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
另外,在 JDK1.8中新增了几个 CAS 的办法,他们的实现是基于下面三个办法做的一层封装:
//1.8新增,给定对象o,依据获取内存偏移量指向的字段,将其减少delta, //这是一个CAS操作过程,直到设置胜利方能退出循环,返回旧值 public final int getAndAddInt(Object o, long offset, int delta) { int v; do { //获取内存中最新值 v = getIntVolatile(o, offset); //通过CAS操作 } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }//1.8新增,办法作用同上,只不过这里操作的long类型数据 public final long getAndAddLong(Object o, long offset, long delta) { long v; do { v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, v + delta)); return v; } //1.8新增,给定对象o,依据获取内存偏移量对于字段,将其 设置为新值newValue, //这是一个CAS操作过程,直到设置胜利方能退出循环,返回旧值 public final int getAndSetInt(Object o, long offset, int newValue) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, newValue)); return v; }// 1.8新增,同上,操作的是long类型 public final long getAndSetLong(Object o, long offset, long newValue) { long v; do { v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, newValue)); return v; } //1.8新增,同上,操作的是援用类型数据 public final Object getAndSetObject(Object o, long offset, Object newValue) { Object v; do { v = getObjectVolatile(o, offset); } while (!compareAndSwapObject(o, offset, v, newValue)); return v; }
CAS在java.util.concurrent.atomic相干类、Java AQS、CurrentHashMap等实现上有十分宽泛的利用。
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2021最新版)
2.终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!