Unsafe类是java中非常特别的一个类。它名字就叫做“不安全”,提供的操作可以直接读写内存、获得地址偏移值、锁定或释放线程。

通过正常途径是无法获得Unsafe实例的,首先它的构造方法是私有的,然后,即使你调用它的getUnsafe方法,也会抛出SecurityException。

A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class islimited because only trusted code can obtain instances of it.

任何关于Unsafe类的文章都不会推荐我们在代码中使用它,但这并不妨碍我们了解它可以做什么。下面我们来看下利用Unsafe类我们是否可以做点有趣的事情。

获取Unsafe实例

public static Unsafe getUnsafeInstance() throws Exception{
Field unsafeStaticField =
Unsafe.class.getDeclaredField("theUnsafe");
unsafeStaticField.setAccessible(true);
return (Unsafe) unsafeStaticField.get(Unsafe.class);
}

通过java反射机制,我们跳过了安全检测,拿到了一个unsafe类的实例。

我找遍了Unsafe类的API,没有发现可以直接获取对象地址的方法,Unsafe中操作地址相关的方法都要求提供一个Object类型的参数,用来获取对象的初始地址。

修改和读取数组中的值

Unsafe u = getUnsafeInstance();

int[] arr = {1,2,3,4,5,6,7,8,9,10};

int b = u.arrayBaseOffset(int[].class);

int s = u.arrayIndexScale(int[].class);

u.putInt(arr, (long)b+s*9, 1);

for(int i=0;i<10;i++){

	int v = u.getInt(arr, (long)b+s*i);

	System.out.print(v+“ ”);

}

打印结果:1 2 3 4 5 6 7 8 9 1 ,可以看到,成功读出了数组中的值,而且最后一个值由10改为了1。

  • arrayBaseOffset: 返回当前数组第一个元素地址相对于数组起始地址的偏移值,在本例中返回6。
  • arrayIndexScale: 返回当前数组一个元素占用的字节数,在本例中返回4。
  • putInt(obj,offset,intval): 获取数组对象obj的起始地址,加上偏移值,得到对应元素的地址,将intval写入内存。
  • getInt(obj,offset): 获取数组对象obj的起始地址,加上偏移值,得到对应元素的地址,从而获得元素的值。
  • 偏移值: 数组元素偏移值 = arrayBaseOffset + arrayIndexScalse * i。

获取对象实例

/** Allocate an instance but do not run any constructor.
Initializes the class if it has not yet been. */
public native Object allocateInstance(Class cls) throws InstantiationException;

allocateInstance: 在不执行构造方法的前提下,获取一个类的实例,即使这个类的构造方法是私有的。

修改静态变量和实例变量的值

先定义一个Test类

public class Test {

	public int intfield ;

	public static int staticIntField;

	public static int[] arr;

	private Test(){
System.out.println("constructor called");
}
}

修改Test类的实例变量

Unsafe u = getUnsafeInstance();

Test t = (Test) u.allocateInstance(Test.class);

long b1 = u.objectFieldOffset(Test.class.getDeclaredField("intfield"));

u.putInt(t, b1, 2);

System.out.println("intfield:"+t.intfield);

这里使用allocateInstance方法获取了一个Test类的实例,并且没有打印“constructor called”,说明构造方法没有调用。

修改实例变量与修改数组的值类似,同样要获取地址偏移值,然后调用putInt方法。

  • objectFieldOffset: 获取对象某个属性的地址偏移值。

我们通过Unsafe类修改了Java堆中的数据。

修改Test类的静态变量

Field staticIntField = Test.class.getDeclaredField("staticIntField");

Object o = u.staticFieldBase(staticIntField);

System.out.prinln(o==Test.class);

Long b4 = u.staticFieldOffset(staticIntField);
//因为是静态变量,传入的Object参数应为class对象
u.putInt(o, b4, 10); System.out.println("staticIntField:"+u.getInt(Test.class, b4));

打印结果:

true

staticIntField:10

静态变量与实例变量不同之处在于,静态变量位于于方法区中,它的地址偏移值与Test类在方法区的地址相关,与Test类的实例无关。

  • staticFieldBase: 获取静态变量所属的类在方法区的首地址。可以看到,返回的对象就是Test.class。
  • staticFieldOffset: 获取静态变量地址偏移值。

我们通过Unsafe类修改了方法区中的信息。

调戏String.intern

在jdk7中,String.intern不再拷贝string对象实例,而是保存第一次出现的对象的引用。在下面的代码中,通过Unsafe修改被引用对象s的私有属性value达到间接修改s1的效果!

String s = "abc";

//保存s的引用
s.intern(); //此时s1==s,地址相同
String s1 = "abc"; Unsafe u = getUnsafeInstance(); //获取s的实例变量value
Field valueInString = String.class.getDeclaredField("value"); //获取value的变量偏移值
long offset = u.objectFieldOffset(valueInString); //value本身是一个char[],要修改它元素的值,仍要获取baseOffset和indexScale
long base = u.arrayBaseOffset(char[].class); long scale = u.arrayIndexScale(char[].class); //获取value
char[] values = (char[]) u.getObject(s, offset); //为value赋值
u.putChar(values, base + scale, 'c'); System.out.println("s:"+s+" s1:"+s1); //将s的值改为 abc
s = "abc"; String s2 = "abc"; String s3 = "abc"; System.out.println("s:"+s+" s1:"+s1);

打印结果:

s:acc s1:acc

s:acc s1:acc s2:acc s3:acc

我们发现了什么?所有值为“abc”的字符串都变成了“acc”!!!

Unsafe类果然不安全!!!

版权声明:本文为博主原创文章,未经博主允许不得转载.

Unsafe类初探的更多相关文章

  1. 【实战Java高并发程序设计 1】Java中的指针:Unsafe类

    是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我 ...

  2. Java sun.misc.Unsafe类的学习笔记

    Java未开源的Unsafe类 Unsafe类可以为我们提供高效并且线程安全方式操作变量,直接和内存数据打交道. 获取Unsafe实体的方法 private static Unsafe getUnsa ...

  3. java编程之:Unsafe类

    Unsafe类在jdk 源码的多个类中用到,这个类的提供了一些绕开JVM的更底层功能,基于它的实现可以提高效率.但是,它是一把双刃剑:正如它的名字所预示的那样,它是 Unsafe的,它所分配的内存需要 ...

  4. Java中Unsafe类详解

    http://www.cnblogs.com/mickole/articles/3757278.html Java不能直接访问操作系统底层,而是通过本地方法来访问.Unsafe类提供了硬件级别的原子操 ...

  5. 说一说Java的Unsafe类

    最近在看Java并发包的源码,发现了神奇的Unsafe类,仔细研究了一下,在这里跟大家分享一下. Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛 ...

  6. Java中的Unsafe类111

    1.Unsafe类介绍 Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty.Hadoo ...

  7. JDK 1.8 sun.misc.Unsafe类CAS底层实现

    在java.util.concurrent包下面的很多类为了追求性能都采用了sun.misc.Unsafe类中的CAS操作,从而避免使用synchronized等加锁方式带来性能上的不足. 在sun. ...

  8. Conccrent中 Unsafe类原理 以及 原子类AutomicXX的原理以及对Unsafe类的使用

    Unsafe类的介绍 Java中基于操作系统级别的原子操作类sun.misc.Unsafe,它是Java中对大多数锁机制实现的最基础类.请注意,JDK 1.8和之前JDK版本的中sun.misc.Un ...

  9. JDK中Unsafe类详解

    Java中Unsafe类详解 在openjdk8下看Unsafe源码 浅析Java中的原子操作 Java并发编程之LockSupport http://hg.openjdk.java.net/jdk7 ...

随机推荐

  1. STM32 内存分配解析及变量的存储位置

    内存映射 在一些桌面程序中,整个内存映射是通过虚拟内存来进行管理的,使用一种称为内存管理单元(MMU)的硬件结构来将程序的内存映射到物理RAM.在对于 RAM 紧缺的嵌入式系统中,是缺少 MMU 内存 ...

  2. 从零开始学习docker之在docker中搭建redis(单机)

    docker搭建redis 一.环境准备 云环境:CentOS 7.6 64位 二.下载镜像 从docker hub中找到redis镜像 传送门------https://hub.docker.com ...

  3. 关于flex弹性布局

    http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

  4. Component Object Model (COM) 是什么?

    本文主要介绍 COM 的基础知识,倾向于理论性的理解,面向初学者,浅尝辄止. 1. COM 是什么: COM 的英文全称是,Component Object Model,中文译为,组件对象模型.它官方 ...

  5. WSFC与DFS

    WSFC里面的文件服务器群集,文件始终是一份,数据始终存放在群集磁盘中,通过群集来维持文件服务器这项服务始终持续可用,在2012之前同一时间WSFC只能有一台节点对外提供文件服务,2012开始群集引入 ...

  6. Norwegian Wood

    0 前言 <挪威的森林>是村上春树很有名的一部小说,但我想大多数人阅读的时候都只是把书名当作一个符号,而不是作为故事去追究. 我国台湾知名文学评论家杨照先生说过:村上的书里有太多太多典故, ...

  7. CodeForces - 1047B Cover Points

    B. Cover Points time limit per test1 second memory limit per test256 megabytes inputstandard input o ...

  8. String-StringBuilder-StringBuffer 的区别

    String StringBuilder StringBuffer 的区别 String:不可改变的字符串,不能够被修改                    (https://baijiahao.b ...

  9. python requests 接口测试

    1.get方法请求接口 url:显而易见,就是接口的地址url啦 headers:请求头,例如:content-type = application/x-www-form-urlencoded par ...

  10. ubuntu 下修改MySQL 的root用户密码

    环境:Ubuntu 16.04  :  Mysql 5.7问题:mysql root登录密码 忘记了..忘记了..忘..了 1.登录MySQL $ mysql -u root -p 输入密码,如果登录 ...