Java Unsafe学习笔记分享

目录
  • sun.misc.Unsafe
  • 获取Unsafe实例
  • 重点API
  • 使用场景
    • 避免初始化
    • 内存崩溃(Memory corruption)
    • 抛出异常(Throw an Exception)
    • 大数组(Big Arrays)
    • 并发(Concurrency)
    • 挂起与恢复
    • Unsafe API
  • 知识点
    • park和unpark的灵活之处

sun.misc.Unsafe

作用:可以用来在任意内存地址位置处读写数据,支持一些CAS原子操作

Java最初被设计为一种安全的受控环境。尽管如此,HotSpot还是包含了一个后门sun.misc.Unsafe,提供了一些可以直接操控内存和线程的底层操作。Unsafe被JDK广泛应用于java.nio和并发包等实现中,这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改,但是不建议在生产环境中使用

获取Unsafe实例

Unsafe对象不能直接通过new Unsafe()或调用Unsafe.getUnsafe()获取,原因如下:

  • 不能直接new Unsafe(),原因是Unsafe被设计成单例模式,构造方法是私有的;
  • 不能通过调用Unsafe.getUnsafe()获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载
@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

获取实例

//方法一:我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath选项,指定系统类路径加上你使用的一个Unsafe路径
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
// 方法二
static {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        UNSAFE = (Unsafe) field.get(null);
    } catch (Exception e) {
    }
}

注意:忽略你的IDE。比如:eclipse显示”Access restriction…”错误,但如果你运行代码,它将正常运行。如果这个错误提示令人烦恼,可以通过以下设置来避免:

Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference -> Warning

重点API

  • allocateInstance(Class<?> var1)不调用构造方法生成对象
User instance = (User) UNSAFE.allocateInstance(User.class);
  • objectFieldOffset(Field var1)返回成员属性在内存中的地址相对于对象内存地址的偏移量
  • putLong,putInt,putDouble,putChar,putObject等方法,直接修改内存数据(可以越过访问权限)
package com.quancheng;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CollectionApp {
    private static sun.misc.Unsafe UNSAFE;
    public static void main(String[] args) {
        try {
            User instance = (User) UNSAFE.allocateInstance(User.class);
            instance.setName("luoyoub");
            System.err.println("instance:" + instance);
            instance.test();
            Field name = instance.getClass().getDeclaredField("name");
            UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(name), "huanghui");
            instance.test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }
}
class User {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public void test() {
        System.err.println("hello,world" + name);
    }
}
  • copyMemory:内存数据拷贝
  • freeMemory:用于释放allocateMemory和reallocateMemory申请的内存
  • compareAndSwapInt/compareAndSwapLongCAS操作
  • getLongVolatile/putLongVolatile

使用场景

避免初始化

当你想要跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类,allocateInstance方法是非常有用的,使用构造器、反射和unsafe初始化它,将得到不同的结果

public class CollectionApp {
    private static sun.misc.Unsafe UNSAFE;
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        A a = new A();
        a.test(); // output ==> 1
        A a1 = A.class.newInstance();
        a1.test(); // output ==> 1
        A instance = (A) UNSAFE.allocateInstance(A.class);
        instance.test(); // output ==> 0
    }
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }
}
class A{
    private long a;
    public A(){
        a = 1;
    }
    public void test(){
        System.err.println("a==>" + a);
    }
}

内存崩溃(Memory corruption)

Unsafe可用于绕过安全的常用技术,直接修改内存变量;实际上,反射可以实现相同的功能。但值得关注的是,我们可以修改任何对象,甚至没有这些对象的引用

Guard guard = new Guard();
guard.giveAccess();   // false, no access
// bypass
Unsafe unsafe = getUnsafe();
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
guard.giveAccess(); // true, access granted

注意:我们不必持有这个对象的引用

  • 浅拷贝(Shallow copy)
  • 动态类(Dynamic classes)

我们可以在运行时创建一个类,比如从已编译的.class文件中。将类内容读取为字节数组,并正确地传递给defineClass方法;当你必须动态创建类,而现有代码中有一些代理, 这是很有用的

private static byte[] getClassContent() throws Exception {
    File f = new File("/home/mishadoff/tmp/A.class");
    FileInputStream input = new FileInputStream(f);
    byte[] content = new byte[(int)f.length()];
    input.read(content);
    input.close();
    return content;
}
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
              null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null); // 1

抛出异常(Throw an Exception)

该方法抛出受检异常,但你的代码不必捕捉或重新抛出它,正如运行时异常一样

getUnsafe().throwException(new IOException());

大数组(Big Arrays)

正如你所知,Java数组大小的最大值为Integer.MAX_VALUE。使用直接内存分配,我们创建的数组大小受限于堆大小;实际上,这是堆外内存(off-heap memory)技术,在java.nio包中部分可用;

这种方式的内存分配不在堆上,且不受GC管理,所以必须小心Unsafe.freeMemory()的使用。它也不执行任何边界检查,所以任何非法访问可能会导致JVM崩溃

class SuperArray {
    private final static int BYTE = 1;
    private long size;
    private long address;
    public SuperArray(long size) {
        this.size = size;
        address = getUnsafe().allocateMemory(size * BYTE);
    }
    public void set(long i, byte value) {
        getUnsafe().putByte(address + i * BYTE, value);
    }
    public int get(long idx) {
        return getUnsafe().getByte(address + idx * BYTE);
    }
    public long size() {
        return size;
    }
}
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
    array.set((long)Integer.MAX_VALUE + i, (byte)3);
    sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum);  // 300

并发(Concurrency)

几句关于Unsafe的并发性。compareAndSwap方法是原子的,并且可用来实现高性能的、无锁的数据结构

挂起与恢复

定义:

public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time); // isAbsolute参数是指明时间是绝对的,还是相对的

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

unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的;比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态,见下例Example1

Example1:
// 针对当前线程已经调用过unpark(多次调用unpark的效果和调用一次unpark的效果一样)
public static void main(String[] args) throws InterruptedException {
    Thread currThread = Thread.currentThread();
    UNSAFE.unpark(currThread);
    UNSAFE.unpark(currThread);
    UNSAFE.unpark(currThread);
    UNSAFE.park(false, 0);
    UNSAFE.park(false, 0);
    System.out.println("SUCCESS!!!");
}
// 恢复线程interrupt() && UNSAFE.unpark()运行结果一样
public static void main(String[] args) throws InterruptedException {
    Thread currThread = Thread.currentThread();
    new Thread(()->{
        try {
            Thread.sleep(3000);
            System.err.println("sub thread end");
            // currThread.interrupt();
            UNSAFE.unpark(currThread);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
    UNSAFE.park(false, 0);
    System.out.println("SUCCESS!!!");
}
// 如果是相对时间也就是isAbsolute为false(注意这里后面的单位纳秒)到期的时候,与Thread.sleep效果相同,具体有什么区别有待深入研究
//相对时间后面的参数单位是纳秒
UNSAFE.park(false, 3000000000l);
System.out.println("SUCCESS!!!");
long time = System.currentTimeMillis()+3000;
//绝对时间后面的参数单位是毫秒
UNSAFE.park(true, time);
System.out.println("SUCCESS!!!");

注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。实际上,park函数即使没有“许可”,有时也会无理由地返回,实际上在SUN Jdk中,object.wait()也有可能被假唤醒;

注意:unpark方法最好不要在调用park前对当前线程调用unpark

Unsafe API

sun.misc.Unsafe类包含105个方法。实际上,对各种实体操作有几组重要方法,其中的一些如下:
Info.仅返回一些低级的内存信息
addressSize
pageSize
Objects.提供用于操作对象及其字段的方法
allocateInstance     ##直接获取对象实例
objectFieldOffset
Classes.提供用于操作类及其静态字段的方法
staticFieldOffset
defineClass
defineAnonymousClass
ensureClassInitialized
Arrays.操作数组
arrayBaseOffset
arrayIndexScale
Synchronization.低级的同步原语
monitorEnter
tryMonitorEnter
monitorExit
compareAndSwapInt
putOrderedInt
Memory.直接内存访问方法
allocateMemory
copyMemory
freeMemory
getAddress
getInt
putInt

知识点

Unsafe.park()当遇到线程终止时,会直接返回(不同于Thread.sleep,Thread.sleep遇到thread.interrupt()会抛异常)

// Thread.sleep会抛异常
public static void main(String[] args) throws InterruptedException {
   Thread thread =  new Thread(()->{
        try {
            System.err.println("sub thread start");
            Thread.sleep(10000);
            System.err.println("sub thread end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
   thread.start();
    TimeUnit.SECONDS.sleep(3);
    thread.interrupt();
    System.out.println("SUCCESS!!!");
}
output==>
sub thread start
SUCCESS!!!
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.quancheng.ConcurrentTest.lambda$main$0(ConcurrentTest.java:13)
    at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
public static void main(String[] args) throws InterruptedException {
   Thread thread =  new Thread(()->{
       System.err.println("sub thread start");
       UNSAFE.park(false,0);
       System.err.println("sub thread end");
    });
   thread.start();
    TimeUnit.SECONDS.sleep(3);
    UNSAFE.unpark(thread);
    System.out.println("SUCCESS!!!");
}
output==>
sub thread start
sub thread end
SUCCESS!!!
Process finished with exit code 0

unpark无法恢复处于sleep中的线程,只能与park配对使用,因为unpark发放的许可只有park能监听到

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        try {
            System.err.println("sub thread start");
            TimeUnit.SECONDS.sleep(10);
            System.err.println("sub thread end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(3);
    UNSAFE.unpark(thread);
    System.out.println("SUCCESS!!!");
}

park和unpark的灵活之处

上面已经提到,unpark函数可以先于park调用,这个正是它们的灵活之处。

一个线程它有可能在别的线程unPark之前,或者之后,或者同时调用了park,那么因为park的特性,它可以不用担心自己的park的时序问题,否则,如果park必须要在unpark之前,那么给编程带来很大的麻烦!!

”考虑一下,两个线程同步,要如何处理?

在Java5里是用wait/notify/notifyAll来同步的。wait/notify机制有个很蛋疼的地方是,比如线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待。编程的时候就会很蛋疼。

另外,是调用notify,还是notifyAll?

notify只会唤醒一个线程,如果错误地有两个线程在同一个对象上wait等待,那么又悲剧了。为了安全起见,貌似只能调用notifyAll了“

park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • java中Unsafe的使用讲解

    目录 1.获取unsafe 2.获取unsafe 前段时间因为看JUC的源码,里面有大量关于unsafe的操作,所以就来看看了.写点笔记总结下(本文基于jdk1.8): unsafe可以帮我们直接去操作硬件资源,当然了是借助java的jit来进行的,官方不推荐使用,因为不安全,例如你使用unsafe创建一个超级大的数组,但是这个数组jvm是不管理的,只能你自己操作,容易oom,也不利于资源的回收. 好了,下面我们来看代码 1.获取unsafe //1.最简单的使用方式是基于反射获取Unsafe实

  • 你一定不知道的Java Unsafe用法详解

    目录 Unsafe是什么 如何正确地获取Unsafe对象 Unsafe实现CAS锁 使用Unsafe创建对象 Unsafe加载类 总结 Unsafe是什么 首先我们说Unsafe类位于rt.jar里面sun.misc包下面,Unsafe翻译过来是不安全的,这倒不是说这个类是不安全的,而是说开发人员使用Unsafe是不安全的,也就是不推荐开发人员直接使用Unsafe.而且Oracle JDK源码包里面是没有Unsafe的源码的.其实JUC包里面的类大部分都用到了Unsafe,可以说Unasfe是j

  • Java基础之Unsafe内存操作不安全类详解

    简介 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着 1.不受jvm管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏. 2.Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉. 3.直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率. Unsafe 类 public final c

  • Java使用Unsafe类的示例详解

    Unsafe 对象提供了非常底层的,操作内存.线程的方法,相当于开了后门. 在atomic类中CAS实现.LockSupport中park unpark的底层都调用了UnSafe中的方法. UnSafe并不是说线程不安全,而是说操作内存有可能会造成不安全问题. 当然对于开发人员来说 Unsafe 对象不能直接调用,只能通过反射获得 通过反射获得Unsafe对象 package com.dongguo.unsafe; import sun.misc.Unsafe; import java.lang

  • Java 中的 Unsafe 魔法类的作用大全

    Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别.不安全操作的方法,如直接访问系统内存资源.自主管理内存资源等,这些方法在提升Java运行效率.增强Java语言底层资源操作能力方面起到了很大的作用. 但是,这个类的作者不希望我们使用它,因为我们虽然我们获取到了对底层的控制权,但是也增大了风险,安全性正是Java相对于C++/C的优势.因为该类在sun.misc包下,默认是被BootstrapClassLoader加载的.如果我们在程序中去调用这个类的话,我们使用的类加载

  • Java CAS操作与Unsafe类详解

    一.复习 计算机内存模型,synchronized和volatile关键字简介 二.两者对比 sychronized和volatile都解决了内存可见性问题 不同点: (1)前者是独占锁,并且存在者上下文切换的开销以及线程重新调度的开销:后者是非阻塞算法,不会造成上下文切换的开销. (2)前者可以保证操作的原子性,但是后者不能保证操作的原子性. 三.在什么情况下才会使用volatile 写入变量是不依赖当前值的,如果是依赖当前值的话,由于获取-计算-写入,三者不是原子性操作,而volatile是

  • Java Unsafe学习笔记分享

    目录 sun.misc.Unsafe 获取Unsafe实例 重点API 使用场景 避免初始化 内存崩溃(Memory corruption) 抛出异常(Throw an Exception) 大数组(Big Arrays) 并发(Concurrency) 挂起与恢复 Unsafe API 知识点 park和unpark的灵活之处 sun.misc.Unsafe 作用:可以用来在任意内存地址位置处读写数据,支持一些CAS原子操作 Java最初被设计为一种安全的受控环境.尽管如此,HotSpot还是

  • Java中反射的学习笔记分享

    目录 简介 一个简单的例子 设置使用反射 模拟instanceof运算 了解类的方法 获取有关构造函数的信息 查找类字段 按名称调用方法 创建新对象 更改字段的值 使用数组 总结 简介 反射是Java编程语言中的一个特性.它允许执行的Java程序检查或 操作 自身,并操作程序的内部属性.例如,Java类可以获取其所有成员的名称并显示它们. 从程序内部检查和操作Java类的能力听起来可能不太显示,但是在其他编程语言中,这个特性根本不存在.例如,在C或C ++ 程序中无法获取有关该程序中定义的函数的

  • JavaWeb学习笔记分享(必看篇)

    自定义列表 <dl></dl>:表示列表的范围 **在里面 <dt></dt>:上层内容 **在里面 <dd></dd>:下层内容 有序列表 <ol></ol>:有序列表的范围 --属性 type:设置排序方式,1(默认),a,i.. **在ol标签里面 <li>具体内容</li> 无序列表 <ul></ul>:无序列表的范围 --属性 type:circle(空

  • Java基础学习笔记之数组详解

    本文实例讲述了Java基础学习笔记之数组.分享给大家供大家参考,具体如下: 数组的定义于使用 1:数组的基本概念 一组相关变量的集合:在Java里面将数组定义为引用数据类型,所以数组的使用一定要牵扯到内存分配:想到了用new 关键字来处理. 2:数组的定义格式 区别: 动态初始化后数组中的每一个元素的内容都是其对应数据类型的默认值,随后可以通过下标进行数组内容的修改: 如果希望数组定义的时候就可以提供内容,则采用静态初始化的方式: a:数组的动态初始化(声明并初始化数组): 数据类型 数组名称

  • Java 注解学习笔记

    注解说明 Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据.为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据.Java语言中的类.方法.变量.参数和包等都可以被标注.和Javadoc不同,Java标注可以通过反射获取注解内容.在编译器生成类文件时,注解可以被嵌入到字节码中.Java虚拟机可以保留注解内容,在运行时可以获取到注解内容. 内置注解 Java定义了一套注解,共有7个,3个在java.lang中,剩下4个

  • Java多线程学习笔记

    目录 多任务.多线程 程序.进程.线程 学着看jdk文档 线程的创建 1.继承Thread类 2.实现Runable接口 理解并发的场景 龟兔赛跑场景 实现callable接口 理解函数式接口 理解线程的状态 线程停止 线程休眠sleep 1.网路延迟 2.倒计时等 线程礼让yield 线程强制执行 观察线程状态 线程的优先级 守护线程 线程同步机制 1.synchronized 同步方法 2.同步块synchronized(Obj){} lock synchronized与lock 多任务.多

  • Bootstrap选项卡学习笔记分享

    本文实例为大家分享了Bootstrap选项卡的学习笔记,供大家参考,具体内容如下 tab选项卡 <body> <div class="container"> <!-- tab选项卡 --> <ul class="nav nav-tabs"> <li class="active"><a href="#pan1" data-toggle="tab"

  • Bootstrap教程JS插件滚动监听学习笔记分享

    本文主要来学习一下JavaScript插件--滚动监听. 1.案例 滚动监听插件可以根据滚动条的位置自动更新所对应的导航标记.你可以试试滚动这个页面,看看左侧导航的变化. 先把实现的代码上了,你可以通过测试代码先来看看效果. <!DOCTYPE html> <html> <head> <title>Bootstrap</title> <meta name="viewport" content="width=de

  • Java语法基础之运算符学习笔记分享

    一.运算符 运算符包括下面几种: 算术运算符赋值运算符比较运算符逻辑运算符位运算符三目运算符 最不常用的是位运算符,但也是最接近计算机底层的. 1.算术运算符 (1)+的几种用法:加法.正数.字符串连接符 (2)除法的时候要注意一个问题:整数相除,只能得到整数.要想得到小数,可以将数据自身*1.0,即将数据自身先转换为浮点型. 2.赋值运算符 符号 = += -= *= /= %= 注:=为基本的赋值运算符,其他的为扩展的赋值运算符 面试题: (1)short s=1, s = s+1; (2)

  • Android自定义控件之开关按钮学习笔记分享

    今天来讲讲自定义单个控件,就拿开关按钮来讲讲,相信大家见了非常多这样的了,先看看效果: 我们可以看到一个很常见的开关按钮,那就来分析分析. 首先: 这是由两张图片构成: ①一张为有开和关的背景图片 ②一张为控制开和关的滑动按钮 第一步: 写个类继承View,并重写几个方法: 第一个为构造函数,重写一个参数的函数和两个参数的函数就够了,因为两个参数的函数能够使用自定义属性 第二个为控制控件的大小–>protected void onMeasure(int widthMeasureSpec, int

随机推荐