计划用一段时间阅读完JDK8中的JUC包下的所有源码,以这一篇作为开头。熟悉JUC的人肯定知道,在JUC中大量使用了sun.misc包中的Unsafe类中的CAS相关的方法,所以作为开篇,我们先阅读Unsafe类提供了那些方法给我们使用。
在JDK8以后,sun包就不再开源了,但是我们可以下载OpenJDK8的源码来进行分析,里面包含了sun包,下载地址:OpenJDK8源码地址
和以往一样,我们先看Unsafe类有那些属性以及有那些构造方法:
public final class Unsafe {
private static native void registerNatives();
static {
registerNatives();
sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
}
可以看到Unsafe类使用了单例模式,获取Unsafe类实例可以使用getUnsafe()静态方法,但是在调用getUnsafe()方法时,需要确保调用getUnsafe()的类是引导类加载器加载时才可以获取到Unsafe类实例,否则抛出SecurityException异常。如果我们想确实想要在自己的类中使用Unsafe类,怎么办呢?一种方法是使用-Xbootclasspath/a参数,使自己的类被引导类加载器加载,另一种方法就会使用放射来获取了:
public class UnsafeUtil {
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe)f.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new SecurityException("get Unsafe failed");
}
}
}
接着我们来看看Unsafe类中提供了那些方法来供我们使用:
一、查看与设置对象值相关的操作:
public native int getInt(Object o, long offset);
通过给定的Java变量获取int类型的值。这里实际上是获取一个Java对象o中,获取偏移地址为offset的属性的值,此方法可以突破修饰符的抑制,也就是无视private、protected和default修饰符。类似的方法有getBoolean、getByte、getShort、getChar、getFloat、getDouble、getObject。
只有当offeet满足下面三个条件之一时,返回的值才是正确的,否则返回的结果是undefined.
- 当获取的值不是静态字段时,offset是通过Unsafe类的objectFieldOffset方法获得的。
- 当获取的值是静态字段时,o是通过Unsafe类的staticFieldBase,offset是通过staticFieldOffset获得的。
- 当o是数组对象时,offset的必须是:B+N*S的形式,其中N是数组有效的索引,B是通过Unsafe类的arrayBaseOffset方法获得,S是通过Unsafe类的arrayIndexScale方法获得;
public native void putInt(Object o, long offset, int x);
将int类型值x,储存到对象o偏移量offset的位置(这里两个参数的含义和getInt方法中的含义是一样的)。类似的方法还有putBoolean、putByte、putShort、putChar、putFloat、putDouble、putObject。
Demo1:演示getInt和putInt的用法:
public class UnsafeTest {
static class Person {
static int age = 20;
int money = 20000;
@Override
public String toString() {
return "【 " + "age : " + age + " , money: " + money + " 】";
}
}
public static void main(String[] args) throws Exception{
Unsafe unsafe = UnsafeUtil.getUnsafe();
Person person = new Person();
Field age = Person.class.getDeclaredField("age");
Field money = person.getClass().getDeclaredField("money");
System.out.println("Test getInt");
System.out.println("==================================================================");
System.out.println("person age: " + unsafe.getInt(unsafe.staticFieldBase(age), unsafe.staticFieldOffset(age)));
System.out.println("person money: " + unsafe.getInt(person, unsafe.objectFieldOffset(money)));
System.out.println("Test putInt");
System.out.println("==================================================================");
unsafe.putInt(unsafe.staticFieldBase(age), unsafe.staticFieldOffset(age), 30);
unsafe.putInt(person, unsafe.objectFieldOffset(money), 500000);
System.out.println(person);
}
}
输出结果如下:
public native int getIntVolatile(Object o, long offset);
此方法和上面的getInt
功能类似,不过附加了'volatile'加载语义,也就是强制从主存中获取属性值。类似的方法有getShortVolatile、getDoubleVolatile等等。这个方法要求被使用的属性被volatile修饰,否则功能和getInt
方法相同。
public native void putIntVolatile(Object o, long offset, int x);
此方法和上面的putInt
功能类似,不过附加了'volatile'加载语义,也就是设置值的时候强制(JMM会保证获得锁到释放锁之间所有对象的状态更新都会在锁被释放之后)更新到主存,从而保证这些变更对其他线程是可见的。类似的方法有putShortVolatile、putDoubleVolatile等等。这个方法要求被使用的属性被volatile修饰,否则功能和putInt
方法相同。
public native void putOrderedInt(Object o, long offset, int x);
设置o对象中offset偏移地址offset对应的Int型field的值为指定值x。这是一个有序或者有延迟的putIntVolatile方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被修改的时候使用才会生效。类似的方法有putOrderedObject
和putOrderedLong
。
二、查询操作:
public native long staticFieldOffset(Field f);
返回给定的静态属性在它的类的存储分配中的位置(偏移地址)。
public native long objectFieldOffset(Field f);
返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)。
public native Object staticFieldBase(Field f);
返回给定的静态属性的位置,配合staticFieldOffset方法使用。
public native boolean shouldBeInitialized(Class<?> c);
检测给定的类是否需要初始化。通常需要使用在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)。 此方法当且仅当ensureClassInitialized
方法不生效的时候才返回false。
public native void ensureClassInitialized(Class<?> c);
检测给定的类是否已经初始化。通常需要使用在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)。
public native int arrayBaseOffset(Class<?> arrayClass);
返回数组类型的第一个元素的偏移地址(基础偏移地址)。如果arrayIndexScale
方法返回的比例因子不为0,你可以通过结合基础偏移地址和比例因子访问数组的所有元素。Unsafe中已经初始化了很多类似的常量如ARRAY_BOOLEAN_BASE_OFFSET等。
public native int arrayIndexScale(Class<?> arrayClass);
返回数组类型的比例因子(其实就是数据中元素偏移地址的增量,因为数组中的元素的地址是连续的)。Unsafe中已经初始化了很多类似的常量如ARRAY_BOOLEAN_INDEX_SCALE等。
Demo2: 演示数组的操作:
public class UnsafeTest {
static class ArrayDemo {
int[] arr = {1,2,3,4,5,6,7,8,9};
}
public static void main(String[] args) {
Unsafe unsafe = UnsafeUtil.getUnsafe();
ArrayDemo arrayDemo = new ArrayDemo();
System.out.println(unsafe.getInt(arrayDemo.arr, (long)Unsafe.ARRAY_INT_BASE_OFFSET + 2 * Unsafe.ARRAY_INT_INDEX_SCALE));
unsafe.putInt(arrayDemo.arr, (long)Unsafe.ARRAY_INT_BASE_OFFSET + 2 * Unsafe.ARRAY_INT_INDEX_SCALE, 10);
System.out.println(unsafe.getInt(arrayDemo.arr, (long)Unsafe.ARRAY_INT_BASE_OFFSET + 2 * Unsafe.ARRAY_INT_INDEX_SCALE));
}
}
输出为:
三:定义类相关的方法:
public native Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);
告诉JVM定义一个类,返回类实例,此方法会跳过JVM的所有安全检查。默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例应该来源于调用者。
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
定义一个匿名类
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
通过Class对象创建一个类的实例,不需要调用其构造函数、初始化代码、JVM安全检查等等。同时,它抑制修饰符检测,也就是即使构造器是private修饰的也能通过此方法实例化。 如果类还没有初始化,将会执行类的初始化代码。
public class UnsafeTest {
static class Demo {
static {
System.out.println("init");
}
int i;
private Demo(int i) {this.i = i;}
}
public static void main(String[] args) throws InstantiationException{
Unsafe unsafe = UnsafeUtil.getUnsafe();
unsafe.allocateInstance(Demo.class);
Demo demo = (Demo) unsafe.allocateInstance(Demo.class);
System.out.println(demo.i);
}
}
输出:
四、内存管理相关:
public native int addressSize();
获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法。
public native int pageSize();
获取本地内存的页数,此值为2的幂次方。
public native long allocateMemory(long bytes);
分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址。返回的内存块的内容不被初始化,那么它们一般会变成内存垃圾。生成的本机指针永远不会为零,并将对所有值类型进行对齐。可以通过freeMemory
方法释放内存块,或者通过reallocateMemory
方法调整内存块大小。bytes值为负数或者过大会抛出IllegalArgumentException异常,如果系统拒绝分配内存会抛出OutOfMemoryError异常。
public native long reallocateMemory(long address, long bytes);
通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)。可以通过freeMemory
方法释放内存块,或者通过reallocateMemory
方法调整内存块大小。bytes值为负数或者过大会抛出IllegalArgumentException异常,如果系统拒绝分配内存会抛出OutOfMemoryError异常。
public native void setMemory(Object o, long offset, long bytes, byte value);
将给定内存块中的所有字节设置为固定值(通常是0)。内存块的地址由对象引用o和偏移地址共同决定,如果对象引用o为null,offset就是绝对地址。第三个参数就是内存块的大小,如果使用allocateMemory
进行内存开辟的话,这里的值应该和allocateMemory
的参数一致。value就是设置的固定值,一般为0。一般而言,o为null。
public void setMemory(long address, long bytes, byte value) {
setMemory(null, address, bytes, value);
}
setMemory的重载。
public native void copyMemory(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes);
内存拷贝,由srcBase与srcOffset来确定基地址,当srcBase为null时,offset就是绝对地址。
public void copyMemory(long srcAddress, long destAddress, long bytes) {
copyMemory(null, srcAddress, null, destAddress, bytes);
}
copyMemory的重载
public native void freeMemory(long address);
释放由allocateMemory于reallocateMemory申请的内存
五、线程与线程同步相关: