static变量到底存在什么位置
static变量到底存在什么位置
在上一篇里面提到了一些关于static变量位置的猜测,现在就来验证一下。
要验证的主要有以下几点:
- class对象(java mirror)是否在堆里
- instanceKlass指针是否接在class对象尾部
- static 变量是否在class对象后加上一个字长的偏移量上
环境
$ java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
为了内存比较“整齐”,关闭压缩指针,启动参数加上-XX:-UseCompressedOops
。
前期准备
用jol打印Class类信息,版本 :
<dependencies>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.6</version>
</dependency>
</dependencies>
import org.openjdk.jol.info.ClassLayout;
public class Test {
public static void main(String[] args){
System.out.println(ClassLayout.parseClass(java.lang.Class.class).toPrintable());
}
}
输出:
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
java.lang.Class object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 8 Constructor Class.cachedConstructor N/A
24 8 Class Class.newInstanceCallerCache N/A
32 8 String Class.name N/A
40 8 (alignment/padding gap) N/A
48 8 SoftReference Class.reflectionData N/A
56 8 ClassRepository Class.genericInfo N/A
64 8 Object[] Class.enumConstants N/A
72 8 Map Class.enumConstantDirectory N/A
80 8 AnnotationData Class.annotationData N/A
88 8 AnnotationType Class.annotationType N/A
96 8 ClassValueMap Class.classValueMap N/A
104 40 (alignment/padding gap) N/A
144 4 int Class.classRedefinedCount N/A
148 4 (loss due to the next object alignment)
Instance size: 152 bytes
Space losses: 48 bytes internal + 4 bytes external = 52 bytes total
Class对象大小为152,那么instanceKlass指针的地址应该在Class对象+152偏移量上,静态变量在160偏移量上。
测试代码:
public class Test {
static long static_field = 0x111;
void fun() {
//断点打在这里
System.out.println("hehe");
}
public static void main(String[] args){
new Test().fun();
}
}
断点打在fun方法的这里,开启调试模式。
查看进程id:
$ jps
1792
6161 Jps
1913 RemoteMavenServer
6123 Launcher
6126 Test
打开HSDB:
sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
连接到6126上:
Screen Shot 2017-01-16 at 12.16.22.png在命令行里面输入universe,输出:
hsdb> universe
Heap Parameters:
ParallelScavengeHeap [ PSYoungGen [ eden = [0x00000001cdb00000,0x00000001ce01ecc8,0x00000001d1b00000] , from = [0x00000001d2580000,0x00000001d2580000,0x00000001d3000000] , to = [0x00000001d1b00000,0x00000001d1b00000,0x00000001d2580000] ] PSOldGen [ [0x0000000123000000,0x0000000123000000,0x000000012db00000] ] ]
可以看到YoungGen地址范围是0x00000001cdb00000 - 0x00000001d3000000,而且YoungGen和OldGen并不连续,在这里面扫描Test
对象。
hsdb> scanoops 0x00000001cdb00000 0x00000001d3000000 Test
0x00000001cdbfce80 Test
Test类没有实例变量(instance variable),所以他的大小是16字节,mark+klass指针(64位关闭压缩指针的情况下)。
hsdb> mem 0x00000001cdbfce80 2
0x00000001cdbfce80: 0x0000000000000001
0x00000001cdbfce88: 0x0000000111d174e0
- mark最后两位(二进制位,而不是最后两位数字)是锁标志,01代表未锁定。
- instanceKlass地址:0x0000000111d174e0
在Inspector中输入0x0000000111d174e0:
Screen Shot 2017-01-16 at 12.37.19.png可以看到java mirror(也就是Class对应的java 对象)地址为:0x00000001cdbfc418
上面static_field已经把静态变量显示出来了,用jshell
把0x111打印出来:
jshell> 0x111
$1 ==> 273
前面已经提到当前环境下Class对象的大小为152,也就是19个字长,再加上我们要查看的instanceKlass指针和long变量,一共是21。
hsdb> mem 0x00000001cdbfc418 21
0x00000001cdbfc418: 0x000000762efe5d01
0x00000001cdbfc420: 0x000000011192b288
0x00000001cdbfc428: 0x0000000000000000
0x00000001cdbfc430: 0x0000000000000000
0x00000001cdbfc438: 0x00000001cdbfce28
0x00000001cdbfc440: 0x00000001cdb7b1a8
0x00000001cdbfc448: 0x00000001cdbfcb10
0x00000001cdbfc450: 0x0000000000000000
0x00000001cdbfc458: 0x0000000000000000
0x00000001cdbfc460: 0x0000000000000000
0x00000001cdbfc468: 0x0000000000000000
0x00000001cdbfc470: 0x0000000000000000
0x00000001cdbfc478: 0x0000000000000000
0x00000001cdbfc480: 0x00000001cdbfbdf0
0x00000001cdbfc488: 0x0000000000000000
0x00000001cdbfc490: 0x0000000000000000
0x00000001cdbfc498: 0x0000000111d174e0
0x00000001cdbfc4a0: 0x0000000000000000
0x00000001cdbfc4a8: 0x0000001500000000
0x00000001cdbfc4b0: 0x0000000000000000
0x00000001cdbfc4b8: 0x0000000000000111
可以看到klass指针并不在class对象的末尾,而是在偏移量17*8上(有点尴尬)。根据上面打印的Class信息,这里是alignment/padding gap。我算了一下,这个空白应该不是补齐,因为开启压缩指针的情况下class对象大小为96,字长12已经很齐了,而class对象尾部的空白是做什么的还有待考证。
java_mirror地址:0x00000001cdbfc418,YoungGen地址范围是0x00000001cdb00000 - 0x00000001d3000000。
jshell> 0x00000001cdb00000l < 0x00000001cdbfc418l && 0x00000001cdbfc418l < 0x00000001d3000000
$10 ==> true
class对象确实在堆里。
总结:
- class对象在堆里 ✅
- instanceKlass指针存在class对象尾部 ❎
- 再往后存的是static变量✅