• JAVA之Unsafe学习笔记


    JAVA之Unsafe学习笔记

    版权声明: https://blog.csdn.net/luoyoub/article/details/79918104
    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;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    获取实例

    //方法一:我们可以令我们的代码“受信任”。运行程序时,使用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) {
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    注意:忽略你的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);
    1
    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);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    内存崩溃(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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    注意:我们不必持有这个对象的引用

    浅拷贝(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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    抛出异常(Throw an Exception)
    该方法抛出受检异常,但你的代码不必捕捉或重新抛出它,正如运行时异常一样

    getUnsafe().throwException(new IOException());
    1
    大数组(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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    并发(Concurrency)
    几句关于Unsafe的并发性。compareAndSwap方法是原子的,并且可用来实现高性能的、无锁的数据结构

    挂起与恢复
    定义:

    public native void unpark(Thread jthread);
    public native void park(boolean isAbsolute, long time); // isAbsolute参数是指明时间是绝对的,还是相对的
    1
    2
    将一个线程进行挂起是通过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!!!");
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    注意,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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    知识点
    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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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!!!");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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或者其它变量来存储状态,不再需要关心对方的状态

    参考资料
    http://ifeve.com/sun-misc-unsafe
    https://www.jianshu.com/p/a16d638bc921
    https://blog.csdn.net/zhxdick/article/details/52003123
    http://www.baeldung.com/java-unsafe
    https://blog.csdn.net/hengyunabc/article/details/28126139
    ---------------------
    版权声明:本文为CSDN博主「忧伤的比目鱼」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/luoyoub/article/details/79918104

  • 相关阅读:
    三元表达式
    迭代器
    python字符串内的自建函数 string.
    shell下的while和if
    正则表达
    nginx下同时做负载均衡和web服务
    nfs匹配nginx服务
    yum安装nginx的负载均衡详解
    samba实战讲解
    python基础之数据类型
  • 原文地址:https://www.cnblogs.com/handsome1013/p/11310382.html
Copyright © 2020-2023  润新知