下午放假睡觉导致晚上睡不着,闲来无聊读了一下AtomicInteger的部分源码(主要想看一下Java的CAS实现方式),发现其使用了一个sun.misc
的Unsafe
类
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update); //此处便是CAS操作
}
compareAndSwapInt的方法声明如下
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
得知compareAndSwapInt
是一个Native方法(由于sun.misc
的类看不到源码,这里展示的源码是由IDEA反编译得到的),暂时不再继续往下深挖,回到AtomicInteger
在AtomicInteger类中Unsafe对象的初始化代码如下
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
于是乎,我照猫画虎写了一段代码
private static Unsafe getUnsafe2() {
return Unsafe.getUnsafe();
}
不出意料的发生了错误
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at club.xyes.nettest.unsafe.UnsafeTest.getUnsafe2(UnsafeTest.java:30)
at club.xyes.nettest.unsafe.UnsafeTest.main(UnsafeTest.java:34)
查看Unsafe.getUnsafe
源码得知,做了类加载器的检查
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
于是乎,反射获取一下
private static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
throw new Error(e);
}
}
成功了
接下来研究一下Unsafe
方法compareAndSwapInt
的valueOffset
参数是何物
package club.xyes.nettest.unsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
public class UnsafeTest {
private static final Unsafe UNSAFE;
private volatile int value;
private int value2;
private long value3;
private long value4;
static {
UNSAFE = getUnsafe();
}
private static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
throw new Error(e);
}
}
public static void main(String[] args) throws Exception {
System.out.println("value " + getOffset(UnsafeTest.class, "value"));
System.out.println("value2 " + getOffset(UnsafeTest.class, "value2"));
System.out.println("value3 " + getOffset(UnsafeTest.class, "value3"));
System.out.println("value4 " + getOffset(UnsafeTest.class, "value4"));
System.out.println("value " + getOffset(AtomicInteger.class, "value"));
}
private static long getOffset(Class<?> klass, String name) throws NoSuchFieldException {
Field field = klass.getDeclaredField(name);
return UNSAFE.objectFieldOffset(field);
}
}
得到的结果如下
value 12
value2 32
value3 16
value4 24
value 12
不考虑最后一个输出,把结果按照offset从小到大排序后得到下面结果
ps:
1 我把field的数据类型和所占字节数量也一起标了出来
2 发现offset是杂乱的,所以我猜测编译器把field的顺序重新排了序
4 int value1 12
8 long value3 16
8 long value4 24
4 int value2 32
是不是发现了神奇的规律,其实这个offset就是这个字段在对象结构中的起始地址(其实是偏移量,为方便起见,以下统称地址),那就不难猜测compareAndSwapInt
是怎么实现的了。
那么新的问题就来了,为什么第一个字段的起始地址的12呢,对象头部的12个自己二究竟是干嘛用的?
首先想到的便是百度(滑稽),搜索java对象头
,得知对象头分为三个部分
markword的格式
原文地址:https://blog.csdn.net/lkforce/article/details/81128115
所以对象hashcode的长度是25位的咯?不清楚,待会儿深扒一下,
关于markword后面慢慢深扒,再回到字段内存的分配方式上看看
为了更直观的看到对象的格式,我使用了jol(java object layout)工具包获取了对象的格式详情
jol地址
public class JolTest {
public static void main(String[] args) {
val obj = new JolTest();
ClassLayout layout = ClassLayout.parseInstance(obj);
System.out.println(layout);
System.out.println(layout.toPrintable());
}
}
得到如下输出
size = 16
club.xyes.nettest.jol.JolTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
注意输出中的loss due to the next object alignment
这行数据,意思是“为了对齐下一个对象造成的内存损失”,也就是说我一个没有声明任何字段的类new出来的对象默认要在对象结尾填充4个字节对齐内存。
0-31 | 32-63 |
---|---|
markword | 类指针 |
类指针 | 为了对齐而存在的数据 |
那我猜测一下,一个没有任何字段类的对象和一个存在一个int字段类的对象的长度都是16byte,测试一下
public class ObjectLengthTest {
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new A()).toPrintable());
System.out.println(ClassLayout.parseInstance(new B()).toPrintable());
}
}
class A {
private int v;
}
class B {
}
这理就不贴输出了,测试结果与猜测一致,我是用jol获取的对象长度,当然可以用序列化到ByteArrayOutputSteram中获取二进制数组长度实现。
此时我又有了一个疑问:当我的类中存在多个字段时,其内存结构有以下几种情况
public class ObjectLengthTest2 {
private int a;
private long b;
private float c;
private double d;
private int e;
private int f;
private int g;
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new ObjectLengthTest2()).toPrintable());
}
}
输出如下
club.xyes.nettest.jol.ObjectLengthTest2 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int ObjectLengthTest2.a 0
16 8 long ObjectLengthTest2.b 0
24 8 double ObjectLengthTest2.d 0.0
32 4 float ObjectLengthTest2.c 0.0
36 4 int ObjectLengthTest2.e 0
40 4 int ObjectLengthTest2.f 0
44 4 int ObjectLengthTest2.g 0
Instance size: 48 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
排序结果为
0-31 | 32-63 |
---|---|
markword | class pointer |
class pointer | field a int |
field b long 64位 占满了 | |
field d double 64位 | |
field c float 32 位 | field e int 32位 |
field f int 32 位 | field g int 32位 |
所以结果就是后一个字段直接怼在前一个字段结束的地址
,好像研究的偏了,我继续去看CAS了。