Java Unsafe类实现原理及测试代码

  • 时间:
  • 1493人关注

这篇文章主要介绍了Java Unsafe类实现原理及测试代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下,另外这篇文章主要知识点是关于Java、Unsafe类、Java类的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子资料:

教程详情电子资料
  • 教程类别:Java类
  • 编辑整理:耿英耀
  • 教程字数:7855字节
  • 阅读时间:大概20分钟
  • 下载本教程(DOC版)
  • 亿级流量Java高并发与网络编程实战
  • 数据结构与算法Java语言描述
  • Java8 函数式编程
  • Java网络编程(第四版)
  • 轻量级Java EE企业应用开发实战
  • Unsafe类介绍

    第一次看到这个类时被它的名字吓到了,居然还有一个类自名Unsafe?读完本文,大家也能发现Unsafe类确实有点不那么安全,它能实现一些不那么常见的功能。

    Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。

    Unsafe类提供了以下这些功能:

    一、内存管理。包括分配内存、释放内存等。

    该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。

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

    二、非常规的对象实例化。

    allocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。

    这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

    三、操作类、对象、变量。

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

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

    四、数组操作。

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

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

    五、多线程同步。包括锁机制、CAS操作等。

    这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。

    其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。

    Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。

    六、挂起与恢复。

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

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

    七、内存屏障。

    这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的,用于定义内存屏障,避免代码重排序。

    loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。

    测试代码

    import com.User;
    import org.junit.Before;
    import org.junit.Test;
    import sun.misc.Unsafe;
     
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.UUID;
    import java.util.concurrent.CountDownLatch;
     
    class User {
     
      public static String USER_CLASS_NAME = "User.class";
      private int age;
      private String name;
     
      public int getAge() {
        return age;
      }
     
      public String getName() {
        return name;
      }
     
      public User(int age, String name) {
        this.age = age;
        this.name = name;
      }
     
      public void setAge(int age) {
        this.age = age;
      }
     
      public void setName(String name) {
        this.name = name;
      }
    }
     
    public class LockTests {
     
      Unsafe unSafe;
      User u = new User(17, "zhangsan");
     
      @Before
      public void before() throws Exception {
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        unSafe = (Unsafe) theUnsafeField.get(Unsafe.class);
      }
     
      @Test
      public void objectFieldOffset() throws Exception {
        // unSafe偏底层的一个Java工具类
        java.util.List users = new ArrayList();
        for (int i = 0; i < 10; i++) {
          Field ageField = User.class.getDeclaredField("age");
          User u = new User(18, "daxin");
          users.add(u);
          //使用内存获取User age字段在内存中的 offset
          // 是相对地址,不是一个绝对地址
          long ageOffset = unSafe.objectFieldOffset(ageField);
          // 每次都相同
          System.out.println("ageOffset = " + ageOffset);
        }
      }
     
      @Test
      public void compareAndSwapInt() throws Exception {
     
        // unSafe偏底层的一个Java工具类
        Field ageField = User.class.getDeclaredField("age");
     
        User u = new User(18, "daxin");
     
        //使用内存获取User age字段在内存中的 offset
        long ageOffset = unSafe.objectFieldOffset(ageField);
     
        // 修改之前的值
        System.out.println(u.getAge());
        // 进行CAS更新, 由于设置18 因此CAS 会成功
        unSafe.compareAndSwapInt(u, ageOffset, 18, 20);
        System.out.println(u.getAge());
     
        // 由于age设置20 进行CAS失败
        unSafe.compareAndSwapInt(u, ageOffset, 18, 22);
        System.out.println(u.getAge());
     
      }
     
      @Test
      public void ensureClassInitialized() {
        System.out.println("==== start ====");
        unSafe.ensureClassInitialized(ClassIsLoad.class);
        // 再次 确认不会报错
        unSafe.ensureClassInitialized(ClassIsLoad.class);
      }
     
      /**
       * AQS 底层的Node链表就是基于这个工具实现的 。
       *
       * @throws Exception
       */
      @Test
      public void getValueByFieldOffset() throws Exception {
        for (int i = 0; i < 10; i++) {
          User u = new User(18, UUID.randomUUID().toString().substring(i, 20));
          int age = unSafe.getInt(u, 12L);
          System.out.println("age = " + age);
     
          // 获取名字 field offset
          Field nameField = User.class.getDeclaredField("name");
          long nameOffset = unSafe.objectFieldOffset(nameField);
          System.out.println("nameOffset = " + nameOffset);
          String name = unSafe.getObject(u, nameOffset) + "";
          System.out.println("name = " + name);
        }
      }
     
      @Test
      public void pageSize() {
        System.out.println("unSafe.pageSize() = " + unSafe.pageSize());
      }
     
      /**
       * AtomicInteger 底层是基于getAndAddInt实现
       */
      @Test
      public void getAndAddInt() throws InterruptedException {
     
        User u = new User(17, "zhangsan");
        CountDownLatch downLatch = new CountDownLatch(10);
        System.out.println("u.getAge() = " + u.getAge());
        for (int i = 0; i < 10; i++) {
     
          new Thread(new Runnable() {
            @Override
            public void run() {
              downLatch.countDown();
              int val = unSafe.getAndAddInt(u, 12L, 1);
              System.out.println(Thread.currentThread().getName() + " val = " + val);
            }
          }).start();
     
        }
        Thread.sleep(5000);
        System.out.println("u.getAge() = " + u.getAge());
      }
     
      @Test
      public void getAndSetInt() throws InterruptedException {
        User u = new User(17, "zhangsan");
        CountDownLatch downLatch = new CountDownLatch(10);
        System.out.println("u.getAge() = " + u.getAge());
        for (int i = 0; i < 10; i++) {
     
          new Thread(new Runnable() {
            @Override
            public void run() {
              downLatch.countDown();
              int val = unSafe.getAndSetInt(u, 12L, 10);
              System.out.println(Thread.currentThread().getName() + " val = " + val);
            }
          }).start();
     
        }
        Thread.sleep(5000);
        System.out.println("u.getAge() = " + u.getAge());
      }
     
     
      @Test
      public void getIntVolatile() {
     
        for (int i = 0; i < 10; i++) {
          u.setAge(i);
          /**
           * @param obj  the object containing the field to modify.
           * @param offset the offset of the integer field within <code>obj</code>.
           * @return
           */
          int age = unSafe.getIntVolatile(u, 12L);
          System.out.println("age = " + age);
        }
      }
     
      // 系统负载采样的接口
      @Test
      public void getLoadAverage() {
        double[] nums = new double[8];
        int val = unSafe.getLoadAverage(nums, 8);
        System.out.println(val);
      }
     
     
      /**
       * //内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
       * public native void loadFence();
       * <p>
       * <p>
       * 参见:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
       */
     
      @Test
      public void loadFence() {
        //java.util.concurrent.locks.StampedLock.validate
        unSafe.loadFence();
      }
     
      /**
       * //内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
       * public native void storeFence();
       * 参见:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
       */
      @Test
      public void storeFence() {
      }
     
      /**
       * //内存屏障,禁止load、store操作重排序
       * public native void fullFence();
       * 参见:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
       */
      @Test
      public void fullFence() {
      }
     
     
      @Test
      public void shouldBeInitialized() {
        boolean shouldBeInitialized = unSafe.shouldBeInitialized(String.class);
        System.out.println(shouldBeInitialized);
        shouldBeInitialized = unSafe.shouldBeInitialized(User.class);
        System.out.println(shouldBeInitialized);
      }
     
      /**
       * synchronized 的一种实现获取锁
       *
       * @throws InterruptedException
       */
      @Test
      public void monitorEnter() throws InterruptedException {
     
        unSafe.monitorEnter(u);
        new Thread(new Runnable() {
          @Override
          public void run() {
            synchronized (u) {
              System.out.println("==u lock got ==");
            }
          }
        }).start();
     
        Thread.sleep(2000);
        unSafe.monitorExit(u);
      }
     
      @Test
      public void compareAndSwap() {
    //    unSafe.compareAndSwapInt(对象, 对象中的字段偏移, 期望值, 设置值)
    //    unSafe.compareAndSwapLong(对象, 对象中的字段偏移, 期望值, 设置值)
    //    unSafe.compareAndSwapObject(对象, 对象中的字段偏移, 期望值, 设置值)
      }
     
      @Test
      public void t() {
        // 方法签名
        // public void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset, long bytes)
        // unSafe.copyMemory();
      }
     
     
    }
     
     
    class ClassIsLoad {
     
      static {
        System.out.println("ClassIsLoad class Is Load !");
      }
    }

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


    上一篇:2020版Python学习路线图(附学习资料)

    下一篇:Elasticsearch py客户端库安装及使用方法

    相关内容

    • 如何用Java实现将容器 Map中的内容保存到数组

      这篇文章主要介绍了Java实现将容器 Map中的内容保存到数组,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

      05-03Java Map内容保存数组

      阅读更多
    • 如何让java程序运行

      04-29java程序运行

      阅读更多
    • java是如何输出的

      04-29java输出的方式

      阅读更多
    • java如何调用接口

      其实对于java调用接口进行获取对方服务器的数据在开发中特别常见,然而一些常用的基础的知识总是掌握不牢,让人容易忘记,写下来闲的时候看看,比回想总会好一些。 总体而言,一些东西知识点一直复制粘贴容易依赖,重要的是会忘记为什么这么写,只有理解到位,或者八九不离十才可以对于随时变化的情况进行分析,如果到家,还可以对别人或自己的进行优化。 如果你在这篇没有找到你想要的,请点击:java如何调用接口方式二 而对于一些知识

      04-27java调用接口的方法

      阅读更多
    • java如何定义数组

      Java中数组定义的三种方式 1.先声明 2.分配空间 3.赋值 public class arr{ public static void main(String[] args){ int[] arr; //先声明 arr=new int[5]; //分配空间 for(int i=0;i5;i++)arr[i]=i*10; //赋值 for(int i=0;i5;i++){ System.out.println(arr[+i+]=+arr[i]); } }} 结果: 方法二: 1.声明并分配空间 2.赋值

      04-27java定义数组方法

      阅读更多
    • JavaScript忍者秘籍

      JavaScript忍者秘籍

      这是由jQuery库创始人编写的一本深入剖析JavaScript语言的书,从不同层次讲述了逐步成为JavaScript高手所需的知识,适合具备一定JavaScript基础知识的读者阅读

      大小:38.6 MBJavaScript

      点击下载
    • 移动网页设计与开发:HTML5+CSS3+JavaScript

      移动网页设计与开发:HTML5+CSS3+JavaScript

      将落伍的网址打导致更为灵便、友善,而且令其充分运用不一样机器设备和电脑浏览器的与众不同优点。根据《移动网页设计与开发HTML5+CSS3+JavaScript》,您将为投身第一线开发充分准备! *內容

      大小:30.2 MB移动开发

      点击下载
    • JavaScript函数式编程

      JavaScript函数式编程

      JavaScript 是近年来非常受瞩目的一门编程语言,它既支持面向对象编程,也支持函数式编程。本书专门介绍JavaScript函数式编程的特性。

      大小:42158 MB MJavaScript

      点击下载
    • Java从小白到大牛(含源码)

      Java从小白到大牛(含源码)

      Java从小白到大牛是一本Java语言学习教程,读者群是零基础小白,通过本书的学习能够成为Java大牛。主要内容包括:Java语法基础、数据类型、运算符、控制语句、数组、字符串、面向对象基础、继承与多态、抽象类与接口、集合框架、异常处理、输入输出和网络编程等技术。

      大小:15.6 MBJava

      点击下载
    • 实战Java高并发程序设计

      实战Java高并发程序设计

      大小:155793 MB Java

      点击下载

    学习笔记

    4小时43分钟前回答

    一篇看懂Java中的Unsafe类

    前言 本文主要给大家介绍了关于Java中Unsafe类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 1.Unsafe类介绍 Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Hadoop、Kafka等。 使用Unsafe可用来直接访问系统内存资源并进行自主管理,Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。 Unsafe可认为是Java中留下的后门,提供了一些低层次操作,如直接内存访问、线程调度等。 官方并不建议使用Unsafe。 下面是使用Unsafe的一些例子。 1.1实例化私有类 import java.lan……

    9小时17分钟前回答

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

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