iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java Unsafe学习笔记分享
  • 709
分享到

Java Unsafe学习笔记分享

2024-04-02 19:04:59 709人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录sun.misc.Unsafe获取Unsafe实例重点api使用场景避免初始化内存崩溃(Memory corruption)抛出异常(Throw an Exception)大数组

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或者其它变量来存储状态,不再需要关心对方的状态

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

--结束END--

本文标题: Java Unsafe学习笔记分享

本文链接: https://www.lsjlt.com/news/157171.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java Unsafe学习笔记分享
    目录sun.misc.Unsafe获取Unsafe实例重点API使用场景避免初始化内存崩溃(Memory corruption)抛出异常(Throw an Exception)大数组...
    99+
    2024-04-02
  • Java分布式教程:学习笔记分享!
    Java分布式是一个非常重要的知识点,它可以让我们在多台服务器上部署应用程序,从而提高应用程序的可伸缩性和可靠性。本文将分享一些Java分布式的学习笔记,包括分布式原理、分布式架构、分布式锁、分布式缓存、分布式事务等方面的内容。同时,我们...
    99+
    2023-08-31
    教程 学习笔记 分布式
  • Java中反射的学习笔记分享
    目录简介一个简单的例子设置使用反射模拟instanceof运算了解类的方法获取有关构造函数的信息查找类字段按名称调用方法创建新对象更改字段的值使用数组总结简介 反射是Java编程语言...
    99+
    2022-11-13
    Java 反射
  • Java 打包对象:学习笔记分享
    Java 是一门面向对象的编程语言,它的面向对象特性使得 Java 开发者可以轻松地组织代码和数据。但是,在编写代码的过程中,我们经常需要将一组相关的对象打包在一起进行处理。这就是 Java 打包对象的重要作用。本文将会分享 Java 打...
    99+
    2023-10-03
    打包 对象 学习笔记
  • 打包 Java 对象:学习笔记分享
    Java 是一种常用的编程语言,开发者们常常需要将自己编写的 Java 对象打包成可执行的 JAR 文件。本文将分享一些打包 Java 对象的经验和技巧,以便于读者能够更加轻松地完成这项任务。 一、什么是 JAR 文件 JAR 文件是 J...
    99+
    2023-10-04
    打包 对象 学习笔记
  • python入门学习笔记分享
    一、python的基础语法 1.行连接的方法是最后加上一个‘\' 2.注释:多行注释三引号,#行注释;三引号定义的字符串原样输出(保存原格式) 3.I/O语句:用逗号分隔零个或多个表...
    99+
    2024-04-02
  • ASP API教程:学习笔记分享!
    ASP API是一种常用的Web开发技术,它可以通过API(应用程序接口)来访问和操作数据,从而实现Web应用程序的开发。如果您正在学习ASP API,本文将为您提供一些有用的笔记和代码示例,帮助您更好地理解和应用这项技术。 了解ASP...
    99+
    2023-11-06
    api 教程 学习笔记
  • 学习PHP Path文件:笔记分享!
    PHP是一门流行的服务器端脚本语言,能够与HTML一起使用,可以创建动态页面和交互式网站。在PHP中,Path文件是一种重要的概念,它可以让你在PHP中访问和操作文件系统中的文件和目录。在本篇文章中,我们将深入研究PHP Path文件,分...
    99+
    2023-07-05
    path 文件 学习笔记
  • 如何在 Windows 上学习 Java 编程?分享我的学习笔记!
    在当今数字化时代,计算机编程已经成为一项越来越重要的技能。而 Java 编程语言则是其中最受欢迎和广泛使用的一种语言。在 Windows 系统上学习 Java 编程,无疑是一条高效而且实用的学习路径。在本篇文章中,我将分享我的学习笔记,希望...
    99+
    2023-10-09
    windows 学习笔记 日志
  • 从接口到大数据:Java学习笔记分享
    Java是目前最流行的编程语言之一,它被广泛应用于Web开发、移动应用开发、企业应用开发等领域。本文将分享我在学习Java过程中的一些笔记和心得,内容涵盖从接口到大数据的知识点。 一、接口 接口是Java中的一种特殊的类,它只包含抽象方法和...
    99+
    2023-10-17
    大数据 接口 学习笔记
  • Go编程算法:学习笔记分享
    Go语言是一门现代化的编程语言,它的出现给开发者带来了更多的选择和机会。在Go语言中,算法是一个非常重要的部分。本篇文章将会分享一些关于Go编程算法的学习笔记,帮助读者更好地掌握这门语言。 一、排序算法 排序算法是编程中常见的算法之一,它...
    99+
    2023-06-20
    编程算法 学习笔记 自然语言处理
  • Python 日志如何记录?学习笔记分享!
    Python是一种流行的编程语言,广泛应用于各种领域。当我们在编写Python代码时,我们需要记录应用程序中发生的事件和错误信息。这就是Python日志的作用。Python日志可以帮助我们快速定位应用程序中的问题,以便更快地解决它们。在这篇...
    99+
    2023-08-07
    关键字 日志 学习笔记
  • OpenCV机器学习MeanShift算法笔记分享
    MeanShift算法 Mean shift 是一种机器学习算法,并不仅仅局限于图像上的应用。关于 Mean shift 算法介绍的书和文章很多,这里就不多介绍了。简单的说,Mean...
    99+
    2024-04-02
  • 打包攻略:Go Path 学习笔记分享!
    Go Path 是一个非常重要的概念,它是 Go 语言中的一个环境变量,用于指定 Go 语言的工作目录。在编写 Go 程序时,我们经常需要引用其他的包,而这些包可能是我们自己写的,也可能是其他人写的。因此,我们需要将这些包放在一个统一的目...
    99+
    2023-08-24
    path 教程 打包
  • ASP 响应是什么?学习笔记分享!
    ASP响应是什么?学习笔记分享! ASP(Active Server Pages)是一种基于服务器端的Web应用程序开发技术。在ASP中,响应(Response)是指Web服务器向客户端发送数据的过程。在本文中,我们将介绍ASP响应的基本概...
    99+
    2023-10-18
    响应 学习笔记 分布式
  • 《学习Java分布式:如何记录学习笔记提升学习效率?》
    学习Java分布式:如何记录学习笔记提升学习效率? Java是一种流行的编程语言,它可以用于开发各种应用程序,包括分布式系统。学习Java分布式是学习Java编程的重要部分,但是如何记录学习笔记以提高学习效率是一个值得探讨的问题。在本文中,...
    99+
    2023-07-27
    教程 分布式 学习笔记
  • ASP 面试如何备战?学习笔记分享!
    ASP(Active Server Pages)是一种由微软公司开发的服务器端脚本技术,它可以创建动态网页,并且可以使用多种编程语言,如VBScript、JavaScript等。ASP技术在互联网领域得到广泛应用,因此ASP开发工程师的需...
    99+
    2023-11-07
    面试 学习笔记 django
  • PHP数据类型详解:学习笔记分享!
    PHP数据类型详解:学习笔记分享! 在PHP编程语言中,数据类型是非常重要的一个概念。数据类型决定了变量可以存储哪些类型的数据,以及可以对这些数据进行哪些操作。本篇文章将对PHP中的常见数据类型进行详细的讲解,并通过演示代码的形式,帮助读者...
    99+
    2023-07-09
    数据类型 学习笔记 http
  • ASP和Spring如何结合?学习笔记分享!
    ASP和Spring是两种常见的Web开发框架,ASP是微软公司推出的一种动态Web开发框架,而Spring是Java开发中的重要框架之一。结合这两种框架可以使开发者更加高效地进行Web开发。本文将介绍ASP和Spring如何结合,同时提供...
    99+
    2023-11-14
    spring 学习笔记 http
  • GO语言开发技术:学习笔记分享!
    GO语言是一门高效、可靠、可扩展的编程语言,由Google开发。它的出现解决了许多其他编程语言的问题,例如C++中的指针问题、Java中的垃圾回收等等。 GO语言在现代化的软件开发中,已经被广泛应用,并成为了云计算领域的事实标准。本篇文章...
    99+
    2023-08-02
    学习笔记 开发技术 linux
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作