编程知识 cdmana.com

java源码学习(java-src)之Unsafe

----------------------------------------------------------------------------------------------------------

如果以下内容有错误或者您有不同的见解,请关注我。就是下方图片,扫我留言。
 

----------------------------------------------------------------------------------------------------------

以下是导出的Markdown文件:

jdk.internal.misc

public final class Unsafe

/**

  • A collection of methods for performing low-level, unsafe operations.
  • Although the class and all methods are public, use of this class is
  • limited because only trusted code can obtain instances of it.
  •  
  • <em>Note:</em> It is the resposibility of the caller to make sure
  • arguments are checked before methods of this class are
  • called. While some rudimentary checks are performed on the input,
  • the checks are best effort and when performance is an overriding
  • priority, as when methods of this class are optimized by the
  • runtime compiler, some or all checks (if any) may be elided. Hence,
  • the caller must not rely on the checks and corresponding
  • exceptions! */

【说明】

这个类是一些方法的集合,这写方法可以执行底层的不安全的操作。就是说这个类可以直接使用来读取内存了。

【翻译】 注意:调用者的责任一定要在这个类的方法调用之前检查好参数。需要对一些输入做基本的检查,而且性能问题必须优先考虑且尽最大努力做。(为啥我们要做这些性能检查呢?)因为编译器在优化这个类的方法的时候,一些检查会被省略(elided),因此调用者一定不要依赖这些检查和相应的异常。

【理解】 总而言之,在用这类的时候,请手动检查参数(比如:检查内存地址的合法性等),尤其是获取释放之类的操作,手动处理各种异常。你想靠编译器给你做,估计会很不理想。这也就就是官方其实也不推荐使用unfase这个类,但是貌似很多技术都在用,向netty,elasticsearch中都有使用直接读取内存的这些方法,不为别 就是一个字“太快了”。为啥快?因为直接读取内存,不用将内存的数据放到jvm中了,少了数据拷贝这么一步。

/**

 * Provides the caller with the capability of performing unsafe
 * operations.
 *
 * <p>The returned {@code Unsafe} object should be carefully guarded
 * by the caller, since it can be used to read and write data at arbitrary
 * memory addresses.  It must never be passed to untrusted code.
 *
 * <p>Most methods in this class are very low-level, and correspond to a
 * small number of hardware instructions (on typical machines).  Compilers
 * are encouraged to optimize these methods accordingly.
 *
 * <p>Here is a suggested idiom for using unsafe operations:
 *
 * <pre> {@code
 * class MyTrustedClass {
 *   private static final Unsafe unsafe = Unsafe.getUnsafe();
 *   ...
 *   private long myCountAddress = ...;
 *   public int getCount() { return unsafe.getByte(myCountAddress); }
 * }}</pre>
 *
 * (It may assist compilers to make the local variable {@code final}.)
 */

【翻译】

让调用者可以运行unsafe操作: 返回的这个Unsafe对象需要调用者小心的保护,因为这个对象可以直接读写内存数据,千万不要将这个对象传递给不可信的代码。

【理解】 如果这个类被其他外来的类调用了,会直接操作内存,很不安全。

【翻译】 这个类的大部分方法都是底层的,这些方法都会与一些硬件指令通信,我们鼓励编译时同时给这些代码做优化。

【举个例子,下面有一个我们习惯性使用unsafe操作的用法】

【理解】 就是不直接使用unsafe.getByte,在外层在封装一下,同时将这个类的名字声明一下,这个是个可以信任类。

/**

 * @throws  SecurityException if the class loader of the caller
 *          class is not in the system domain in which all permissions
 *          are granted.
 */

@CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }

注意:CallerSensitive

【翻译】 如果调用者的类的加载器没有本机系统授权,则会抛出SecurityException异常 (Bootstrap ,extension可以调用,我们自己写一个main就不可以调用。)

实例化,较之前1.8的jdk方便了很多: private static final Unsafe U = Unsafe.getUnsafe(); private long myCountAddress = ...; public int getCount() { return unsafe.getByte(myCountAddress); }

/// peek and poke operations

/// (compilers should optimize these to memory ops)

// These work on object fields in the Java heap. // They will not work on elements of packed arrays.

存取操作,编译者需要优化这些操作

这些操作作用于堆中的对象的属性,而不是封装好的数组元素

【理解】 可以直接操作内存对象的属性,因为这些属性都是放在堆中。

public native int getInt(Object o, long offset);

【理解】offset是啥?

(AtomicInteger类中,offset可以使用下面的方法获取,value是AtomicInteger的字段名) U.objectFieldOffset(AtomicInteger.class, "value");

【说明】 java内存模型中64位机器(默认地址压缩)对象头占12byte,之后第一个field从12开始偏移,如果是int则为12(加4byte,12,13,14,15),如果接下来是boolean则继续往后偏移为16(加1byte,16),一次类推接下来的一个类型就从17开始。这个地方和重排序有关。

为什么要重排序呢? 1、减少Padding 2、先排基本类型(先大后小,为啥?减少内存碎片化), 然后引用类型;先长字节后短字节的类型, 如果类型相同长度相同就按声明顺序。

/**

  • Atomically updates Java variable to {@code x} if it is currently
  • holding {@code expected}.
  •  
  • <p>This operation has memory semantics of a {@code volatile} read
  • and write. Corresponds to C11 atomic_compare_exchange_strong.
  •  
  • @return {@code true} if successful */ @HotSpotIntrinsicCandidate public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);

【翻译】

如果当前线程拥有这个变量,就原子更新java变量x

这个操作是异变的(volatile)可读写的内存语法。和C11中的atomic_compare_exchange_strong方法一致。

【理解】 说白了就是当前线程如果拿到对象锁,根据offset来更改对象的值,如果当前值与期望的(expected)值一样,就将x赋值到该filed,并且返回true。

【在原子操作类中】 private static final long VALUE= U.objectFieldOffset(AtomicInteger.class, "value");

/**

  • Atomically sets the value to {@code newValue}
  • if the current value {@code == expectedValue},
  • with memory effects as specified by {@link VarHandle#compareAndSet}.
  •  
  • @param expectedValue the expected value
  • @param newValue the new value
  • @return {@code true} if successful. False return indicates that
  • the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expectedValue, int newValue) { return U.compareAndSetInt(this, VALUE, expectedValue, newValue); }
  • atomic_compare_exchange_strong C11 方法:(就是根据内存地址的指针修改值) bool atomic_compare_exchange_strong(volatile A* object, C * expected, C desired); bool atomic_compare_exchange_strong(A* object, C * expected, C desired);
  • 【翻译】 如果当前值和期望值相等,原子操作设置newValue。内存就会被指定的compareAndSet操作而影响。

返回false说明,实际值与期望值不等。

/**

 * Atomically increments the current value,
 * with memory effects as specified by {@link VarHandle#getAndAdd}.
 *
 * <p>Equivalent to {@code addAndGet(1)}.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    return U.getAndAddInt(this, VALUE, 1) + 1;
}

【翻译】

原子地将当前值加一,有啥内存影响,请参见VarHandle#getAndAdd方法的具体说说明

这个方法等同于addAndGet(1) 返回值是更新之后的值

【进一步说明】 U.getAndAddInt(this, VALUE, 1) + 1这个方法:(进入到Unsafe当中查看)

// The following contain CAS-based Java implementations used on
// platforms not supporting native instructions

/**
 * Atomically adds the given value to the current value of a field
 * or array element within the given object {@code o}
 * at the given {@code offset}.
 *
 * @param o object/array to update the field/element in
 * @param offset field/element offset
 * @param delta the value to add
 * @return the previous value
 * @since 1.8
 */
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

// The following contain CAS-based Java implementations used on

// platforms not supporting native instructions

/**
 * Atomically adds the given value to the current value of a field
 * or array element within the given object {@code o}
 * at the given {@code offset}.
 *
 * @param o object/array to update the field/element in
 * @param offset field/element offset
 * @param delta the value to add
 * @return the previous value
 * @since 1.8
 */
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

【翻译】

以下方法包含了基于CAS的java的一些实现(其实就是我们先实现这几个方法,因为这些方法别处会用到,比如AtomicInteger中就用到了),当平台不支持本地指令的时候就可以使用下面这些方法了。

原子地将当前指定的内存地址中的字段或者数据元素的值加上给定的值(也就是说将当前内存地址中的值拿出来加上delta这个参数指定的值,再放回去)

参数: o:对象的字段或者数组的元素 offset:对象字段或者数组元素的内存偏移量 delta:当前内存中的值和这个值相加 返回之前的值

【理解】 这个方法中是一个死循环,每次去循环去内存中读取当前值,如果当前值等于期望值就将数据改为v+delta,如果不相等,就会一直循环 !!!这一直循环就是我们之前说的为啥耗cpu的原因。因为它会一直循环去获取这个volatile修饰的对象的值,我们就说他是乐观锁(CAS属于乐观锁),因为它不等待而是靠自己去争取。和悲观锁相反,悲观锁(Synchronized)就是sleep等着。追求女孩子也如是积极追求总能得到幸福,天天角落里傻等的人注定悲观不敢面对。

除了以上我们会在程序中经常看到的方法之外,还有很多功能:

源码中///用这个放个slash的地方都是: /// peek and poke operations /// (compilers should optimize these to memory ops) 读取数据(上面已经介绍了)

/// helper methods for validating various types of objects/values 帮助方法用来校验不同对象(值)的类型,像校验参数是否合法等等。下面在内存分配的时候,就会去校验你要申请内存大小(size)的有效性。

/// wrappers for malloc, realloc, free: 封装器用来分配释放内存

校验地地址是否合法:

private void checkNativeAddress(long address) { if (ADDRESS_SIZE == 4) { // Accept both zero and sign extended pointers. A valid // pointer will, after the +1 below, either have produced // the value 0x0 or 0x1. Masking off the low bit allows // for testing against 0. if ((((address >> 32) + 1) & ~1) != 0) { throw invalidInput(); } } }

  • 【说明】 ADDRESS_SIZE ==4,ADDRESS_SIZE 到底是个啥?他是以下这个变量: public static final int ADDRESS_SIZE = ADDRESS_SIZE0; 那么问题来了?ADDRESS_SIZE0又是啥? /**
    • The size in bytes of a native pointer, as stored via {@link
    • #putAddress}. This value will be either 4 or 8. Note that the
    • sizes of other primitive types (as stored in native memory
    • blocks) is determined fully by their information content.
    •  
    • @implNote
    • The actual value for this field is injected by the JVM. */ static final int ADDRESS_SIZE0;

【翻译】 这个本地指针的字节大小是通过putAddress存入的,这个只值么是4要么是8,注意:基本配型的大小完全取决于存储的内容。

!!! 这个ADDRESS_SIZE 的实际值是JVM注入的,也就是说和JVM有关。其实从字面上来理解也可以,就是地址(address)大小(size),就是内存地址的大小(用bytes表示)。之前也提到过,使用unsafe要自己做校验,其实就是说需要校验内存地址的值是否合法这一块。一不小心就出错,所以也是官方不太建议使用unsafe的原因之一吧。

内存分配:

/** * Allocates a new block of native memory, of the given size in bytes. The * contents of the memory are uninitialized; they will generally be * garbage. The resulting native pointer will never be zero, and will be * aligned for all value types. Dispose of this memory by calling {@link * #freeMemory}, or resize it with {@link #reallocateMemory}. * * <em>Note:</em> It is the resposibility of the caller to make * sure arguments are checked before the methods are called. While * some rudimentary checks are performed on the input, the checks * are best effort and when performance is an overriding priority, * as when methods of this class are optimized by the runtime * compiler, some or all checks (if any) may be elided. Hence, the * caller must not rely on the checks and corresponding * exceptions! * * @throws RuntimeException if the size is negative or too large * for the native size_t type * * @throws OutOfMemoryError if the allocation is refused by the system */ public long allocateMemory(long bytes) { allocateMemoryChecks(bytes);

    if (bytes == 0) {
        return 0;
    }

    long p = allocateMemory0(bytes);
    if (p == 0) {
        throw new OutOfMemoryError();
    }

    return p;
}
  • 【翻译】 分配一个新的内存区域,大小通过bytes指定。内存存储的内容不会初始化,即使初始化内容通常情况下也会成为垃圾,产生的这个指针呢永远不会是0,而且这个指针会和其它的类型一样保持内存对齐(说明一下:内存对齐就是在使用volatile修饰时防止重排序的实质是一样的,其实就是按照类型在内存中将指针排序,节约内存。)。通过freeMemory释放内存,使用reallocateMemory来调整内存大小。
  • 注意:调用者的责任一定要在这个类的方法调用之前检查好参数。需要对一些输入做基本的检查,而且性能问题必须优先考虑且尽最大努力做。(为啥我们要做这些性能检查呢?)因为编译器在优化这个类的方法的时候,一些检查会被省略(elided),因此调用者一定不要依赖这些检查和相应的异常。
  • 如果参数是负数或者值太大,则抛出RuntimeException

如果分配被操作系统拒绝(内存不足)则抛出OutOfMemoryError

/**

 * Tells the VM to define a class, without security checks.  By default, the
 * class loader and protection domain come from the caller's class.
 */
public Class<?> defineClass(String name, byte[] b, int off, int len,
                            ClassLoader loader,
                            ProtectionDomain protectionDomain) {
    if (b == null) {
        throw new NullPointerException();
    }
    if (len < 0) {
        throw new ArrayIndexOutOfBoundsException();
    }

    return defineClass0(name, b, off, len, loader, protectionDomain);
}

/// random queries

随意的查询操作,主要是查询内存地址等等之类的操作。

/**

  • This constant differs from all results that will ever be returned from
  • {@link #staticFieldOffset}, {@link #objectFieldOffset},
  • or {@link #arrayBaseOffset}. */ public static final int INVALID_FIELD_OFFSET = -1;

这个变量和从对象或者数组获取偏移量的方法中返回的结果不同。(字面理解就是,这个-1不是一个内存地址)

/**

 * Reports the location of a given field in the storage allocation of its
 * class.  Do not expect to perform any sort of arithmetic on this offset;
 * it is just a cookie which is passed to the unsafe heap memory accessors.
 *
 * <p>Any given field will always have the same offset and base, and no
 * two distinct fields of the same class will ever have the same offset
 * and base.
 *
 * <p>As of 1.4.1, offsets for fields are represented as long values,
 * although the Sun JVM does not use the most significant 32 bits.
 * However, JVM implementations which store static fields at absolute
 * addresses can use long offsets and null base pointers to express
 * the field locations in a form usable by {@link #getInt(Object,long)}.
 * Therefore, code which will be ported to such JVMs on 64-bit platforms
 * must preserve all bits of static field offsets.
 * @see #getInt(Object, long)
 */
public long objectFieldOffset(Field f) {
    if (f == null) {
        throw new NullPointerException();
    }

    return objectFieldOffset0(f);
}

报告一个类中存储分配的指定field的内存地址,不要对这个值进行任何的算数运算,因为它仅仅是一个小文件(cookie)并传递给了了当前不安全的堆内存存储器。

任何指定的字段都有一个确切的偏移量和基准数,任何两个不同的字段的偏移量和基准数都是不同的。

1.4.1版本之后,字段的偏移量用长整型表示,尽管Sun JVM不使用最有意义的32位,但是JVM在实现绝对地址中存储静态变量时仍然可以用长整型和null基准指针以getInt可用的形式表示取到的字段地址。

因此,代码迁移到64位平台时必须保留所有静态字段的偏移量的所有位数。

为什么要保留偏移量的所有位数呢?我也知道,Todo带研究。

/// random trusted operations from JNI:

可以随意访问JNI的可信的操作 /** * Tells the VM to define a class, without security checks. By default, the * class loader and protection domain come from the caller's class. */ public Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) { if (b == null) { throw new NullPointerException(); } if (len < 0) { throw new ArrayIndexOutOfBoundsException(); }

    return defineClass0(name, b, off, len, loader, protectionDomain);
}

public native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                    ClassLoader loader,
                                    ProtectionDomain protectionDomain);

【翻译】

告诉虚拟机定义一个类,不需要做安全检查,默认情况下类加载器和保护主线程都来自调用者的类。

【参数说明】 String name:类的名字 byte[] b:二进制字节文件class int off:偏移量,一般从0开始 int len:长度,一般是字节码文件内容的长度 ClassLoader loader:类加载器(负责将 Class 的字节码形式转换成内存形式的 Class 对象) ProtectionDomain protectionDomain:保护区域(target/classes下面的类,即使本机的类,定义了classes下面类或资源文件的访问权限)

  • 【protectionDomain,保护区域,就是定义一些文件的(类,资源文件,配置文件等)访问权限】 java.security.Permissions@46fbb2c1 ( ("java.lang.RuntimePermission" "exitVM") ("java.io.FilePermission" "D:\idea\test\target\classes-" "read") )

/**

 * Atomically updates Java variable to {@code x} if it is currently
 * holding {@code expected}.
 *
 * <p>This operation has memory semantics of a {@code volatile} read
 * and write.  Corresponds to C11 atomic_compare_exchange_strong.
 *
 * @return {@code true} if successful
 */
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetReference(Object o, long offset,
                                                   Object expected,
                                                   Object x);

增加一个引用类型来解决ABA问题

其次,我们也可以增加一个栈顶指针来解决ABA问题,当线程更新时候,先判定当前的栈顶指针是否是当前线程中存的指针,如果变了,就自旋。

版权声明
本文为[todayido]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/1995706/blog/4836888

Scroll to Top