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

使用Unsafe可用来直接访问系统内存资源并进行自主管理,Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。

Unsafe可认为是Java中留下的后门,提供了一些低层次操作,如直接内存访问、线程调度等。

官方并不建议使用Unsafe。

获取Unsafe对象

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

*不能直接 new Unsafe() ,原因是 Unsafe 被设计成单例模式,构造方法是私有的;

*不能通过调用 Unsafe.getUnsafe()获取,因为 getUnsafe 被设计成只能从引导类加载器(bootstrap class loader)加载,从 getUnsafe 的源码中也可以看出来,如下:

    @CallerSensitive
    public static Unsafe getUnsafe() {
        //得到调用该方法的Class对象
        Class cc = Reflection.getCallerClass();
        //判断调用该方法的类是否是引导类加载器(bootstrap class loader)
        //如果不是的话,比如由AppClassLoader调用该方法,则抛出SecurityException异常
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        //返回单例对象
        return theUnsafe;

虽然我们不能通过以上方法得到Unsafe对象,但得Unsafe类中有个私有的静态全局属性theUnsafe(Unsafe实例对象),通过反射,可以获取到该成员属性theUnsafe对应的Field对象,并将其设置为可访问,从而得到theUnsafe具体对象,如下代码: 

package concurrency;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
import sun.reflect.Reflection;
public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException {
        // 通过反射得到theUnsafe对应的Field对象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 设置该Field为可访问
        field.setAccessible(true);
        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe);
 

Unsafe类源码分析 

Unsafe的大部分API都是native的方法,通过调用JNI的代码实现的(JNI:Java Native Interface为JAVA本地调用),主要包括以下几类:

1)Class相关。主要提供Class和它的静态字段的操作方法。

2)Object相关。主要提供Object和它的字段的操作方法。

3)Arrray相关。主要提供数组及其中元素的操作方法。

4)并发相关。主要提供低级别同步原语,如CAS、线程调度、volatile、内存屏障等。

5)Memory相关。提供了直接内存访问方法(绕过Java堆直接操作本地内存),可做到像C一样自由利用系统内存资源。

6)系统相关。主要返回某些低级别的内存信息,如地址大小、内存页大小。

一个类,方便后续举例 

public class VO
	public int a = 0;
	public long b = 0;
	public static String c= "123";
	public static Object d= null;
	public static int e = 100;

1.Class相关 

//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class<?> c);
//确保类被初始化
public native void ensureClassInitialized(Class<?> c);
//定义一个类,可用于动态创建类
public native Class<?> defineClass(String name, byte[] b, int off, int len,
     ClassLoader loader,
     ProtectionDomain protectionDomain);
//定义一个匿名类,可用于动态创建类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

获取静态字段的属性值 

VO.e = 1024;
Field sField = VO.class.getDeclaredField("e");
Object base = unsafe.staticFieldBase(sField);
long offset = unsafe.staticFieldOffset(sField);
System.out.println(unsafe.getInt(base, offset));//1024

 2.Object相关

//获得对象的字段偏移量 
public native long objectFieldOffset(Field f); 
//获得给定对象地址偏移量的int值
public native int getInt(Object o, long offset);
//设置给定对象地址偏移量的int值
public native void putInt(Object o, long offset, int x);
//创建对象,但并不会调用其构造方法。如果类未被初始化,将初始化类。
public native Object allocateInstance(Class<?> cls)
 throws InstantiationException;

读取对象实例字段的值 

//获取实例字段的属性值
VO vo = new VO();
vo.a = 10000;
long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
int va = unsafe.getInt(vo, aoffset);
System.out.println("va="+va);

3.数组相关 

* Report the offset of the first element in the storage allocation of a * given array class. If {@link #arrayIndexScale} returns a non-zero value * for the same class, you may use that scale factor, together with this * base offset, to form new offsets to access elements of arrays of the * given class. * @see #getInt(Object, long) * @see #putInt(Object, long, int) //返回数组中第一个元素的偏移地址 public native int arrayBaseOffset(Class<?> arrayClass); //boolean、byte、short、char、int、long、float、double,及对象类型均有以下方法 /** The value of {@code arrayBaseOffset(boolean[].class)} */ public static final int ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class); * Report the scale factor for addressing elements in the storage * allocation of a given array class. However, arrays of "narrow" types * will generally not work properly with accessors like {@link * #getByte(Object, int)}, so the scale factor for such classes is reported * as zero. * @see #arrayBaseOffset * @see #getInt(Object, long) * @see #putInt(Object, long, int) //返回数组中每一个元素占用的大小 public native int arrayIndexScale(Class<?> arrayClass); //boolean、byte、short、char、int、long、float、double,及对象类型均有以下方法 /** The value of {@code arrayIndexScale(boolean[].class)} */ public static final int ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);

获取数组的头部大小和元素大小 

// 数组第一个元素的偏移地址,即数组头占用的字节数
int[] intarr = new int[0];
System.out.println(unsafe.arrayBaseOffset(intarr.getClass()));
// 数组中每个元素占用的大小
System.out.println(unsafe.arrayIndexScale(intarr.getClass()));

通过arrayBaseOffset和arrayIndexScale可定位数组中每个元素在内存中的位置。 

4.并发相关 

4.1CAS相关

CAS:CompareAndSwap,内存偏移地址offset,预期值expected,新值x。如果变量在当前时刻的值和预期值expected相等,尝试将变量的值更新为x。如果更新成功,返回true;否则,返回false。

//更新变量值为x,如果当前值为expected
//o:对象 offset:偏移量 expected:期望值 x:新值
public final native boolean compareAndSwapObject(Object o, long offset,
       Object expected,
       Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
      int expected,
      int x);
public final native boolean compareAndSwapLong(Object o, long offset,
      long expected,
      long x);

 从Java 8开始,Unsafe中提供了以下方法:

public final int getAndAddInt(Object o, long offset, int delta) { int v; v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; public final long getAndAddLong(Object o, long offset, long delta) { long v; v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, v + delta)); return v; public final int getAndSetInt(Object o, long offset, int newValue) { int v; v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, newValue)); return v; public final long getAndSetLong(Object o, long offset, long newValue) { long v; v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, newValue)); return v; public final Object getAndSetObject(Object o, long offset, Object newValue) { Object v; v = getObjectVolatile(o, offset); } while (!compareAndSwapObject(o, offset, v, newValue)); return v;

4.2线程调度相关 

//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁
public native void monitorEnter(Object o);
//释放对象锁
public native void monitorExit(Object o);
//尝试获取对象锁,返回true或false表示是否获取成功
public native boolean tryMonitorEnter(Object o);

 4.3volatile相关读写

Java中的基本类型(boolean、byte、char、short、int、long、float、double)及对象引用类型都有以下方法。

//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
//相当于getObject(Object, long)的volatile版本
public native Object getObjectVolatile(Object o, long offset);
//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
//相当于putObject(Object, long, Object)的volatile版本
public native void putObjectVolatile(Object o, long offset, Object x);
 * Version of {@link #putObjectVolatile(Object, long, Object)}
 * that does not guarantee immediate visibility of the store to
 * other threads. This method is generally only useful if the
 * underlying field is a Java volatile (or if an array cell, one
 * that is otherwise only accessed using volatile accesses).
public native void putOrderedObject(Object o, long offset, Object x);
/** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */
public native void putOrderedInt(Object o, long offset, int x);
/** Ordered/Lazy version of {@link #putLongVolatile(Object, long, long)} */
public native void putOrderedLong(Object o, long offset, long x);

4.4内存屏障相关

Java 8引入 ,用于定义内存屏障,避免代码重排序。

//内存屏障,禁止load操作重排序,即屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序,即屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();

5.直接内存访问(非堆内存)

allocateMemory所分配的内存需要手动free(不被GC回收)

//(boolean、byte、char、short、int、long、float、double)都有以下get、put两个方法。 
//获得给定地址上的int值
public native int getInt(long address);
//设置给定地址上的int值
public native void putInt(long address, int x);
//获得本地指针
public native long getAddress(long address);
//存储本地指针到给定的内存地址
public native void putAddress(long address, long x);
//分配内存
public native long allocateMemory(long bytes);
//重新分配内存
public native long reallocateMemory(long address, long bytes);
//初始化内存内容
public native void setMemory(Object o, long offset, long bytes, byte value);
//初始化内存内容
public void setMemory(long address, long bytes, byte value) {
 setMemory(null, address, bytes, value);
//内存内容拷贝
public native void copyMemory(Object srcBase, long srcOffset,
    Object destBase, long destOffset,
    long bytes);
//内存内容拷贝
public void copyMemory(long srcAddress, long destAddress, long bytes) {
 copyMemory(null, srcAddress, null, destAddress, bytes);
//释放内存
public native void freeMemory(long address);

 6.系统相关

//返回指针的大小。返回值为4或8。
public native int addressSize();
/** The value of {@code addressSize()} */
public static final int ADDRESS_SIZE = theUnsafe.addressSize();
//内存页的大小。
public native int pageSize();

我们注意到上面有一个方法

  • stateOffset=unsafe.objectFieldOffset(field) 从方法名上可以这样理解:获取object对象的属性Field的偏移量。

要理解这个偏移量,需要先了解java的内存模型

Java内存模型

Java对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding),简单的理解:

  • 对象头,对象是什么?
  • 实例数据,对象里有什么?
  • 对齐填充,不关键,目的是补齐位数达到8的倍数。

举个简单的例子,如下类:

class VO {
    public int a = 0;
    public int b = 0;

 VO vo=new VO();的时候,Java内存中就开辟了一块地址,包含一个固定长度的对象头(假设是16字节,不同位数机器/对象头是否压缩都会影响对象头长度)+实例数据(4字节的a+4字节的b)+padding。

这里直接说结论,我们上面说的偏移量就是在这里体现,如上面a属性的偏移量就是16,b属性的偏移量就是20。

在unsafe类里面,我们发现一个方法unsafe.getInt(object, offset);
通过unsafe.getInt(vo, 16) 就可以得到vo.a的值。是不是联想到反射了?其实java的反射底层就是用的UNSAFE

进一步思考

如何知道一个类里面每个属性的偏移量?只根据偏移量,java怎么知道读取到哪里为止是这个属性的值?

查看属性偏移量,推荐一个工具类jol:http://openjdk.java.net/projects/code-tools/jol/
用jol可以很方便的查看java的内存布局情况,结合一下代码讲解

public class VO {
    public int a = 0;
    public long b = 0;
    public String c= "123";
    public Object d= null;
    public int e = 100;
    public static int f= 0;
    public static String g= "";
    public Object h= null;
    public boolean i;
public static void main(String[] args) throws Exception {
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseClass(VO.class).toPrintable());
        System.out.println("=================");
        Unsafe unsafe = getUnsafeInstance();
        VO vo = new VO();
        vo.a=2;
        vo.b=3;
        vo.d=new HashMap<>();
        long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
        System.out.println("aoffset="+aoffset);
        // 获取a的值
        int va = unsafe.getInt(vo, aoffset);
        System.out.println("va="+va);
    public static Unsafe getUnsafeInstance() throws Exception {
        // 通过反射获取rt.jar下的Unsafe类
        Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeInstance.setAccessible(true);
        // return (Unsafe) theUnsafeInstance.get(null);是等价的
        return (Unsafe) theUnsafeInstance.get(Unsafe.class);

在我本地机器测试结果如下: 

# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
com.ha.net.nsp.product.VO object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0    12                    (object header)                           N/A
     12     4                int VO.a                                      N/A
     16     8               long VO.b                                      N/A
     24     4                int VO.e                                      N/A
     28     1            boolean VO.i                                      N/A
     29     3                    (alignment/padding gap)                  
     32     4   java.lang.String VO.c                                      N/A
     36     4   java.lang.Object VO.d                                      N/A
     40     4   java.lang.Object VO.h                                      N/A
     44     4                    (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
=================
aoffset=12

 在结果中,我们发现:

  • 1、我本地的虚拟机环境是64位并且开启了compressed压缩,对象都是8字节对齐
  • 2、VO类的内存布局包含12字节的对象头,4字节的int数据,8字节的long数据,其他String和Object是4字节,最后还有4字节的对齐。
  • 3、VO类属性的内存布局跟属性声明的顺序不一致。
  • 4、VO类的static属性不在VO的内存布局中,因为他是属于class类。
  • 5、通过VO类就可以确定一个对象占用的字节数,这个占用空间在编译阶段就已经确定(注:此占用空间并不是对象的真实占用空间,)。
  • 6、如上,通过偏移量12就可以读取到此处存放的值是2。

引申出新的问题:
1、这里的对象头为什么是12字节?对象头里都具体包含什么?
答:正常情况下,对象头在32位系统内占用一个机器码也就是8个字节,64位系统也是占用一个机器码16个字节。但是在我本地环境是开启了reference(指针)压缩,所以只有12个字节。
2、这里的String和Object为什么都是4字节?
答:因为String或者Object类型,在内存布局中,都是reference类型,所以他的大小跟是否启动压缩有关。未启动压缩的时候,32位机器的reference类型是4个字节,64位是8个字节,但是如果启动压缩后,64位机器的reference类型就变成4字节。
3、Java怎么知道应该从偏移量12读取到偏移量16呢,而不是读取到偏移量18或者20?
答:这里我猜测,虚拟机在编译阶段,就已经保留了一个VO类的偏移量数组,那12后的偏移量就是16,所以Java直到读到16为止。

注:一般使用时: 

public class Test implements java.io.Serializable {
    //序列化相关
    private static final long serialVersionUID = 6214790243416807050L;
    // JDK里使用的一个工具类对象,提供一些不安全的操作的方法,一般不会在自己的程序中使用该类
    //在这里主要用到其中的objectFieldOffset、putOrderedInt、compareAndSwapInt方法
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //value成员属性的内存地址相对于对象内存地址的偏移量
    private static final long valueOffset;
    static {
      try {
        //初始化valueOffset,通过unsafe.objectFieldOffset方法获取成员属性value内存地址相对于对象内存地址的偏移量
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    //int的值,设为volatile,保证线程之间的可见性
    private volatile int value;
    ......
                    前言Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Hadoop、Kafka等。使用Unsafe可用来直接访问系统内存资源并进行自主管理,Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。Unsafe可认为是Java中留下的后门,提供...
转载:https://leokongwq.github.io/2016/12/31/java-magic-unsafe.html
Java是一个安全的编程语言,它能最大程度的防止程序员犯一些低级的错误(大部分是和内存管理有关的)。但凡是不是绝对的,使用Unsafe程序员就可以操作内存,因此可能带来一个安全隐患。
这篇文章是就快速学习下sun.misc.Unsafe的公共API和一些有趣的使用例子。
2.Unsafe 实例化
在使用Unsafe之前我们需要先实例化它。但我们不能通过像Unsafe .
import java.lang.reflect.Field;
 * This class should provide access to low-level operations and its
 * use should be limited to trusted code.  Fields can be accessed using
				
Java是一门安全的编程语言,防止程序员犯很多愚蠢的错误,它们大部分是基于内存管理的。但是,有一种方式可以有意的执行一些不安全、容易犯错的操作,那就是使用Unsafe类。 本文是sun.misc.Unsafe公共API的简要概述,及其一些有趣的用法。 Unsafe 实例 在使用Unsafe之前,我们需要创建Unsafe对象的实例。这并不像Unsafe unsafe = new Unsafe()这么简单,因为Unsafe的构造器是私有的。它也有一个静态的getUnsafe()方法,但如果你直接调用Unsafe
本文主要介绍一下JDK中sun.misc包下的一个特别的类—Unsafe类,该类提供了一些底层操作,该类设计的初衷是用于Java核心类库的内部调用,而不是给普通用户使用。 2.获得一个Unsafe实例 首先,要想使用Unsafe类,需要获得一个类的实例,该类的设计是用来内部调用的,而没有直接给出类的实例方法。获得实例的方法就是通过静态方法getUnsafe(),但这样会抛出一个Se...
sun.misc.Unsafe是jdk中为了方便使用java语言灵活操作内存预留的类,由于该类是直接操作内存,所以从java的角度被定义为不安全的,也就是类名的由来。 经常分析jdk源码的同学肯定不陌生,因为jdk中很多地方都用到了这个类。 创建实例:、 sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe() 主要方法如下,本质都是传入一个...
以下sun.misc.Unsafe源码和demo基于jdk1.8; 遗憾的是,Unsafe对象不能直接通过new Unsafe()或调用Unsafe.getUnsafe()获取,原因如下: 不能直接new Unsafe...
这个问题通常是由于Java版本不兼容或者依赖库缺失导致的。可以尝试以下解决方案: 1. 确认Java版本是否符合要求。如果是使用较老的Java版本,可以升级到最新的Java版本试试。 2. 确认依赖库是否完整。如果是使用Maven或Gradle等构建工具管理依赖库,可以尝试清除本地仓库并重新下载依赖库。 3. 如果是使用IDE,可以尝试重新导入项目或清除缓存并重新编译。 4. 如果是在服务器上运行程序,可以尝试重新安装Java并设置正确的环境变量。 如果以上方法都不能解决问题,可以尝试在搜索引擎中查找相关解决方案或者向相关社区或论坛寻求帮助。
其实String对象存储于字符串常量池还是堆中不是看new还是不new,应该是看编译时还是运行时。编译时字面量字符串就加入字符串常量池,在运行时创建的变量就不会加入字符串常池(除非你手动调用intern方法)。 我简单实验了一下: String a = "a" + "a"; String a1 = "aa"; System.out.println(a == a1); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String sb = reader.readLine(); String b = sb + "b"; String b1 = "bb"; System.out.println(b == b1); String c = new String("c") + "c"; String c1 = "cc"; System.out.println(c == c1); String d = new String("d") + new String("d"); String d1 = "dd"; System.out.println(d == d1); b(我手动输入的) false false false Activity工作流入门篇 Activity 2301_77997304: 我第一次就看到这个了!感觉知道了不少