java Unsafe详细解析

问题

(1)Unsafe是什么?

(2)Unsafe只有CAS的功能吗?

(3)Unsafe为什么是不安全的?

(4)怎么使用Unsafe?

简介

Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。

但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本功能。

获取Unsafe的实例

查看Unsafe的源码我们会发现它提供了一个getUnsafe()的静态方法。

@CallerSensitive
public static Unsafe getUnsafe() {
 Class var0 = Reflection.getCallerClass();
 if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
 throw new SecurityException("Unsafe");
 } else {
 return theUnsafe;
 }
}

但是,如果直接调用这个方法会抛出一个SecurityException异常,这是因为Unsafe仅供java内部类使用,外部类不应该使用它。

那么,我们就没有方法了吗?

当然不是,我们有反射啊!查看源码,我们发现它有一个属性叫theUnsafe,我们直接通过反射拿到它即可。

public class UnsafeTest {
 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 f.setAccessible(true);
 Unsafe unsafe = (Unsafe) f.get(null);
 }
}

使用Unsafe实例化一个类

假如我们有一个简单的类如下:

class User {
 int age;

 public User() {
 this.age = 10;
 }
}

如果我们通过构造方法实例化这个类,age属性将会返回10。

User user1 = new User();
// 打印10
System.out.println(user1.age);

如果我们调用Unsafe来实例化呢?

User user2 = (User) unsafe.allocateInstance(User.class);
// 打印0
System.out.println(user2.age);

age将返回0,因为Unsafe.allocateInstance()只会给对象分配内存,并不会调用构造方法,所以这里只会返回int类型的默认值0。

修改私有字段的值

使用Unsafe的putXXX()方法,我们可以修改任意私有字段的值。

public class UnsafeTest {
 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 f.setAccessible(true);
 Unsafe unsafe = (Unsafe) f.get(null);

 User user = new User();
 Field age = user.getClass().getDeclaredField("age");
 unsafe.putInt(user, unsafe.objectFieldOffset(age), 20);

 // 打印20
 System.out.println(user.getAge());
 }
}

class User {
 private int age;

 public User() {
 this.age = 10;
 }

 public int getAge() {
 return age;
 }
}

一旦我们通过反射调用得到字段age,我们就可以使用Unsafe将其值更改为任何其他int值。(当然,这里也可以通过反射直接修改)

抛出checked异常

我们知道如果代码抛出了checked异常,要不就使用try...catch捕获它,要不就在方法签名上定义这个异常,但是,通过Unsafe我们可以抛出一个checked异常,同时却不用捕获或在方法签名上定义它。

// 使用正常方式抛出IOException需要定义在方法签名上往外抛
public static void readFile() throws IOException {
 throw new IOException();
}
// 使用Unsafe抛出异常不需要定义在方法签名上往外抛
public static void readFileUnsafe() {
 unsafe.throwException(new IOException());
}

使用堆外内存

如果进程在运行过程中JVM上的内存不足了,会导致频繁的进行GC。理想情况下,我们可以考虑使用堆外内存,这是一块不受JVM管理的内存。

使用Unsafe的allocateMemory()我们可以直接在堆外分配内存,这可能非常有用,但我们要记住,这个内存不受JVM管理,因此我们要调用freeMemory()方法手动释放它。

假设我们要在堆外创建一个巨大的int数组,我们可以使用allocateMemory()方法来实现:

class OffHeapArray {
 // 一个int等于4个字节
 private static final int INT = 4;
 private long size;
 private long address;

 private static Unsafe unsafe;
 static {
 try {
 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 f.setAccessible(true);
 unsafe = (Unsafe) f.get(null);
 } catch (NoSuchFieldException e) {
 e.printStackTrace();
 } catch (IllegalAccessException e) {
 e.printStackTrace();
 }
 }

 // 构造方法,分配内存
 public OffHeapArray(long size) {
 this.size = size;
 // 参数字节数
 address = unsafe.allocateMemory(size * INT);
 }

 // 获取指定索引处的元素
 public int get(long i) {
 return unsafe.getInt(address + i * INT);
 }
 // 设置指定索引处的元素
 public void set(long i, int value) {
 unsafe.putInt(address + i * INT, value);
 }
 // 元素个数
 public long size() {
 return size;
 }
 // 释放堆外内存
 public void freeMemory() {
 unsafe.freeMemory(address);
 }
}

在构造方法中调用allocateMemory()分配内存,在使用完成后调用freeMemory()释放内存。

使用方式如下:

OffHeapArray offHeapArray = new OffHeapArray(4);
offHeapArray.set(0, 1);
offHeapArray.set(1, 2);
offHeapArray.set(2, 3);
offHeapArray.set(3, 4);
offHeapArray.set(2, 5); // 在索引2的位置重复放入元素

int sum = 0;
for (int i = 0; i < offHeapArray.size(); i++) {
 sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);

offHeapArray.freeMemory();

最后,一定要记得调用freeMemory()将内存释放回操作系统。

CompareAndSwap操作

JUC下面大量使用了CAS操作,它们的底层是调用的Unsafe的CompareAndSwapXXX()方法。这种方式广泛运用于无锁算法,与java中标准的悲观锁机制相比,它可以利用CAS处理器指令提供极大的加速。

比如,我们可以基于Unsafe的compareAndSwapInt()方法构建线程安全的计数器。

class Counter {
 private volatile int count = 0;

 private static long offset;
 private static Unsafe unsafe;
 static {
 try {
 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 f.setAccessible(true);
 unsafe = (Unsafe) f.get(null);
 offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
 } catch (NoSuchFieldException e) {
 e.printStackTrace();
 } catch (IllegalAccessException e) {
 e.printStackTrace();
 }
 }

 public void increment() {
 int before = count;
 // 失败了就重试直到成功为止
 while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) {
 before = count;
 }
 }

 public int getCount() {
 return count;
 }
}

我们定义了一个volatile的字段count,以便对它的修改所有线程都可见,并在类加载的时候获取count在类中的偏移地址。

在increment()方法中,我们通过调用Unsafe的compareAndSwapInt()方法来尝试更新之前获取到的count的值,如果它没有被其它线程更新过,则更新成功,否则不断重试直到成功为止。

我们可以通过使用多个线程来测试我们的代码:

Counter counter = new Counter();
ExecutorService threadPool = Executors.newFixedThreadPool(100);

// 起100个线程,每个线程自增10000次
IntStream.range(0, 100)
 .forEach(i->threadPool.submit(()->IntStream.range(0, 10000)
 .forEach(j->counter.increment())));

threadPool.shutdown();

Thread.sleep(2000);

// 打印1000000
System.out.println(counter.getCount());

park/unparkJVM

在上下文切换的时候使用了Unsafe中的两个非常牛逼的方法park()和unpark()。

当一个线程正在等待某个操作时,JVM调用Unsafe的park()方法来阻塞此线程。

当阻塞中的线程需要再次运行时,JVM调用Unsafe的unpark()方法来唤醒此线程。

我们之前在分析java中的集合时看到了大量的LockSupport.park()/unpark(),它们底层都是调用的Unsafe的这两个方法。

总结

使用Unsafe几乎可以操作一切:

(1)实例化一个类;

(2)修改私有字段的值;

(3)抛出checked异常;

(4)使用堆外内存;

(5)CAS操作;

(6)阻塞/唤醒线程;

彩蛋

论实例化一个类的方式?

(1)通过构造方法实例化一个类;

(2)通过Class实例化一个类;

(3)通过反射实例化一个类;

(4)通过克隆实例化一个类;

(5)通过反序列化实例化一个类;

(6)通过Unsafe实例化一个类;

public class InstantialTest {

 private static Unsafe unsafe;
 static {
 try {
 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 f.setAccessible(true);
 unsafe = (Unsafe) f.get(null);
 } catch (NoSuchFieldException e) {
 e.printStackTrace();
 } catch (IllegalAccessException e) {
 e.printStackTrace();
 }
 }

 public static void main(String[] args) throws Exception {
 // 1. 构造方法
 User user1 = new User();
 // 2. Class,里面实际也是反射
 User user2 = User.class.newInstance();
 // 3. 反射
 User user3 = User.class.getConstructor().newInstance();
 // 4. 克隆
 User user4 = (User) user1.clone();
 // 5. 反序列化
 User user5 = unserialize(user1);
 // 6. Unsafe
 User user6 = (User) unsafe.allocateInstance(User.class);

 System.out.println(user1.age);
 System.out.println(user2.age);
 System.out.println(user3.age);
 System.out.println(user4.age);
 System.out.println(user5.age);
 System.out.println(user6.age);
 }

 private static User unserialize(User user1) throws Exception {
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://object.txt"));
 oos.writeObject(user1);
 oos.close();

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://object.txt"));
 // 反序列化
 User user5 = (User) ois.readObject();
 ois.close();
 return user5;
 }

 static class User implements Cloneable, Serializable {
 private int age;

 public User() {
 this.age = 10;
 }

 @Override
 protected Object clone() throws CloneNotSupportedException {
 return super.clone();
 }
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java中的魔法类:sun.misc.Unsafe示例详解

    前言 Unsafe类在jdk 源码的多个类中用到,这个类的提供了一些绕开JVM的更底层功能,基于它的实现可以提高效率.但是,它是一把双刃剑:正如它的名字所预示的那样,它是Unsafe的,它所分配的内存需要手动free(不被GC回收).Unsafe类,提供了JNI某些功能的简单替代:确保高效性的同时,使事情变得更简单. 这个类是属于sun.* API中的类,并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文档,更可悲的是,它也没有比较好的代码文档. 这篇文章主要是以下文章的整理.翻译.

  • Java并发编程学习之Unsafe类与LockSupport类源码详析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar 中 Unsafe 类主要函数讲解, Unsafe 类提供了硬件级别的原子操作,可以安全的直接操作内存变量,其在 JUC 源码中被广泛的使用,了解其原理为研究 JUC 源码奠定了基础. 首先我们先了解Unsafe类中主要方法的使用,如下: 1.long objectFieldOffset(Field

  • 简单谈一谈Java中的Unsafe类

    Unsafe类是啥? Java最初被设计为一种安全的受控环境.尽管如此,Java HotSpot还是包含了一个"后门",提供了一些可以直接操控内存和线程的低层次操作.这个后门类--sun.misc.Unsafe--被JDK广泛用于自己的包中,如java.nio和java.util.concurrent.但是丝毫不建议在生产环境中使用这个后门.因为这个API十分不安全.不轻便.而且不稳定.这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改.有时它可以被用来在不

  • Java中unsafe操作实例总结

    Unsafe是Java无锁操作的基石,在无锁并发类中都少不了它们的身影,比如ConcurrentHashMap, ConcurrentLinkedQueue, 都是由Unsafe类来实现的.相对于与Java中的锁,它基本无开销,会原地等待.本文主要介绍下Unsafe中的主要操作. 1 compareAndSwap /** * 比较obj的offset处内存位置中的值和期望的值,如果相同则更新.此更新是不可中断的. * * @param obj 需要更新的对象 * @param offset ob

  • 一篇看懂Java中的Unsafe类

    前言 本文主要给大家介绍了关于Java中Unsafe类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 1.Unsafe类介绍 Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty.Hadoop.Kafka等. 使用Unsafe可用来直接访问系统内存资源并进行自主管理,Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用. Un

  • java Unsafe详细解析

    问题 (1)Unsafe是什么? (2)Unsafe只有CAS的功能吗? (3)Unsafe为什么是不安全的? (4)怎么使用Unsafe? 简介 Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用. 但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本功能. 获取Unsafe的实例 查看Unsafe的源码我们会发现它提供了一个getUnsafe()的静态方法. @CallerSens

  • Java中详细解析Map接口

    目录 Map详解: Map基本操作: hashMap原理: Put方法: Get方法: Map的遍历: TreeMap LinkedHashMap: 对比下Hashmap.Hashtable和ConcurrentHashmap: 总结 Map详解: 先看图,便于宏观了解Map的地位. Map接口中键和值一一映射. 可以通过键来获取值. 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值. 当访问的值不存在的时候,方法就会抛出一个NoSuchElementEx

  • java容器详细解析

    前言:在java开发中我们肯定会大量的使用集合,在这里我将总结常见的集合类,每个集合类的优点和缺点,以便我们能更好的使用集合.下面我用一幅图来表示 其中淡绿色的表示接口,红色的表示我们经常使用的类. 1:基本概念 Java容器类类库的用途是保存对象,可以将其分为2个概念. 1.1:Collection 一个独立元素的序列,这些元素都服从一条或多条规则.其中List必须按照插入的顺序保存元素.Set不能有重复的元素.Queue按照排队规则来确定对象的产生顺序(通常也是和插入顺序相同) 1.2:Ma

  • java 值Document解析xml详细介绍

    java 值Document解析xml详细介绍 使用jar包:jdom.jar 配置文件格式 global.xml 一.获取输入的值组成的结点 我们将每个结点使用"." 拼接起来, 如结点i ,拼接为abc.def.i, 注意不包含root.解析每个结点的名字. /** * 获取对应结点的名字 * @param name 不包含root结点的拼接,ie: abc.def.i * @return */ private String[] parsePropertyName(String n

  • java中Optional的使用详细解析

    Optional的使用详解 1.Optional介绍 Optional 类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. Optional 是个容器:它可以保存类型T的值,或者仅仅保存null.Optional提供很多有用的方法,这样我们就不用显式进行空值检测. Optional 类的引入很好的解决空指针异常. 2.构建Optional 构建一个Optional对象:方法有:empty( ).of( ).ofNullable

  • Java关于重排链表详细解析

    1.题目 给定一个单链表 L 的头节点 head ,单链表 L 表示为:  L0→ L1 → - → Ln-1 → Ln  请将其重新排列后变为: L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → - 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换.  来源:力扣(LeetCode) 2.解析 将一个链表分为两个子链表,然后将其归并. 我们要先找到链表的中间节点,在中间节点将其断开 然后反转后半链表 再将两个子链表逐个连起来  将后半链表反转,独立成一个子链表. 最

  • Java 泛型考古 泛型擦除 包装类详细解析

    目录 一. 什么是泛型 二. 为什么要有泛型 ? 示例 三.泛型考古 四.泛型擦除 五.包装类 六.装箱拆箱 一. 什么是泛型 泛型(generic type)其本质是将类型参数化,也就是说所操作的数据类型被指定为一个参数这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. 二. 为什么要有泛型 ? 之前写过MyArrayList顺序表,这个类当时自己在实现的时候只能用一种类型来表示,也就是用的时候自己实现的MyArrayList只能应用于一种类型,要想应用于其他类型

  • Java详细解析==和equals的区别

    目录 1.== 解析 2.equals 方法解析 3.equals方法具有以下特性 1.== 解析 == 常用于相同的基本数据类型之间的比较,也可用于相同类型的对象之间的比较: 如果 == 比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等: 如果 == 是比较的两个对象,那么比较的是两个对象的引用,那么就是比较两个对象的引用是否相等,也就是判断两个对象是否指向了同一块内存区域: 2.equals 方法解析 equals 方法主要用于两个对象之间,检测一个对象是否等于另一个对象. 我

  • 关于java中@Async异步调用详细解析附代码

    目录 前言 1. @Async讲解 2. 用法 2.1 同步调用 2.2 异步调用 3. 自定义线程池 前言 异步调用与同步调用 同步调用:顺序执行,通过调用返回结果再次执行下一个调用 异步调用:通过调用,无需等待返回结果,执行下一个调用 1. @Async讲解 其@Async的注解代码如下: @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public

  • Java 负载均衡算法作用详细解析

    目录 前言 轮询算法 随机算法 加权随机算法 加权轮询算法 源地址hash算法 最小请求数算法 前言 负载均衡在Java领域中有着广泛深入的应用,不管是大名鼎鼎的nginx,还是微服务治理组件如dubbo,feign等,负载均衡的算法在其中都有着实际的使用 负载均衡的核心思想在于其底层的算法思想,比如大家熟知的算法有 轮询,随机,最小连接,加权轮询等,在现实中不管怎么配置,都离不开其算法的核心原理,下面将结合实际代码对常用的负载均衡算法做一些全面的总结. 轮询算法 轮询即排好队,一个接一个的轮着

随机推荐