欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

JAVA中的Unsafe类看着一篇就够了

时间:2023-08-01
文章目录

Unsafe类

Unsafe简介Unsafe对象的创建一、读写相关(包括普通读写,volatile读写,有序写入等)普通读写volatile读写有序写入二、内存操作(包括分配内存、释放内存等)三、操纵对象属性四、操纵数组元素

1、unsafe操作数组相关方法介绍 五、线程挂起与恢复六、CAS

1.什么是CAS?2.典型应用 七、内存屏障 Unsafe类

java版本JDK1.8
本文首发于CSDN
主要参考Java多线程进阶(十二)—— J.U.C之atomic框架:Unsafe类

Unsafe简介

在正式的开讲 juc-atomic框架系列之前,有必要先来了解下Java中的Unsafe类。

Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。

我们进入到Unsafe类中你就会发现,他的方法都是native方法,他们调用本地接口(JNI)访问本地C++实现库来实现功能。

Unsafe使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。

其实咱们在之前就见过它很多次了。

J.U.C中的许多CAS方法,内部其实都是Unsafe类在操作。

比如AtomicBoolean的compareAndSet方法:

public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u);}

其中它的本质就是调用了unsafe.compareAndSwapInt方法。

//(如果对象中的字段值与期望值相等,则将字段值修改为x,然后返回true;否则返回public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);false):

它是一个native方法。

Unsafe类中CAS方法都是native方法,需要通过CAS原子指令完成。在讲AQS时,里面有许多涉及CLH队列的操作,其实就是通过Unsafe类完成的指针操作。

Unsafe对象的创建

Unsafe是一个final类,不能被继承,也没有公共的构造器,它使用了单例模式,只能通过工厂方法getUnsafe获得Unsafe的单例。

public final class Unsafe { private static final Unsafe theUnsafe; private Unsafe() { } @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); //限制了调用该方法的类的类加载器必须为BootstrapClassLoader if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }}

但是getUnsafe方法限制了调用该方法的类的类加载器必须为*BootstrapClassLoader*。

Java中的类加载器可以大致划分为以下三类:

类加载器名称作用Bootstrap类加载器(Bootstrap ClassLoader)主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是JVM自身的一部分,它负责将 【JDK的安装目录】/lib路径下的核心类库,如rt.jar扩展类加载器(Extension ClassLoader)该加载器负责加载【JDK的安装目录】jrelibext目录中的类库,开发者可以直接使用该加载器系统类加载器(Application ClassLoader)负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,也是默认的类加载器

所以在用户代码中直接调用getUnsafe方法,会抛出异常。因为用户自定义的类一般都是由系统类加载器加载的。

但是,是否就真的没有办法获取到Unsafe实例了呢?当然不是,要获取Unsafe对象的方法很多,这里给出一种通过反射的方法:

private static Unsafe getUnsafe(){ try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); return unsafe; } catch (Exception e) { e.printStackTrace(); } return null;}

但是,除非对Unsafe的实现非常清楚,否则应尽量避免直接使用Unsafe来进行操作。

再从Unsafe的功能入手,它有着下面这几个功能:

读写相关内存操作操纵对象属性操纵数组元素线程挂起与恢复CAS内存操作

我们一项项的分析它。

一、读写相关(包括普通读写,volatile读写,有序写入等) 普通读写

通过Unsafe可以读写一个类的属性,即使这个属性是私有的,也可以对这个属性进行读写。

读写一个Object属性的相关方法

public native int getInt(Object var1, long var2);public native void putInt(Object var1, long var2, int var4);

getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。其他的primitive type也有对应的方法。

Unsafe还可以直接在一个地址上读写

public native byte getByte(long var1);public native void putByte(long var1, byte var3);

getByte用于从指定内存地址处开始读取一个byte。putByte用于从指定内存地址写入一个byte。其他的primitive type也有对应的方法。

volatile读写

普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。

public native int getIntVolatile(Object var1, long var2);public native void putIntVolatile(Object var1, long var2, int var4);

getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int。putIntVolatile方法用于在对象指定偏移地址处volatile写入一个int。

volatile读写相对普通读写是更加昂贵的,因为需要保证可见性和有序性,而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。

有序写入

有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。

public native void putOrderedObject(Object var1, long var2, Object var4);public native void putOrderedInt(Object var1, long var2, int var4);public native void putOrderedLong(Object var1, long var2, long var4);

二、内存操作(包括分配内存、释放内存等)

这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。

//分配内存, 相当于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);

getXXX和putXXX包含了各种基本类型的操作。

利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。

三、操纵对象属性

这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。

通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

四、操纵数组元素 1、unsafe操作数组相关方法介绍

//获取数组第一个元素的偏移地址public native int arraybaseOffset(Class arrayClass);//获取数组中元素的增量地址public native int arrayIndexScale(Class arrayClass);//对某个地址的Object赋值public native void putObjectVolatile(Object o, long offset, Object x);//获取某个地址的Object的值public native Object getObjectVolatile(Object o, long offset);

这部分包括了arraybaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arraybaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。

由于Java的数组最大值为nteger.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。

五、线程挂起与恢复

public native void unpark(Object var1);public native void park(boolean var1, long var2);public native void monitorEnter(Object var1);public native void monitorExit(Object var1);public native boolean tryMonitorEnter(Object var1);

这部分包括了park、unpark等方法。

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

monitorEnter方法和monitorExit方法用于加锁,Java中的synchronized锁就是通过这两个指令来实现的。

六、CAS

JUC中大量运用了CAS操作,可以说CAS操作是JUC的基础,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:

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);

1.什么是CAS?

CAS即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,**对此Unsafe提供了一系列的CAS方法(如compareAndSwapXXX)**底层实现即为CPU指令cmpxchg。

2.典型应用

CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。

CAS原理详见文章【并发编程的基石】CAS机制 (compareAndSwap)

七、内存屏障

public native void loadFence();public native void storeFence();public native void fullFence();

loadFence:保证在这个屏障之前的所有读操作都已经完成。
storeFence:保证在这个屏障之前的所有写操作都已经完成。
fullFence:保证在这个屏障之前的所有读写操作都已经完成。

这是在Java 8新引入的三个内存屏障函数,用于定义内存屏障,避免代码重排序。

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。