- 浏览: 3048915 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
(Disclaimer:如果需要转载请先与我联系;
作者:RednaxelaFX -> rednaxelafx.iteye.com)
接着前天的与昨天的帖,今天也来介绍一个HotSpot的Serviceability Agent(以下简称SA)的玩法例子。
昨天用SA把x86机器码反汇编到汇编代码,或许对多数Java程序员来说并不怎么有趣。那么今天就来点更接近Java,但又经常被误解的话题——HotSpot的GC堆的permanent generation。
要用SA里最底层的API来连接上一个Java进程并不困难,不过SA还提供了更方便的封装:只要继承 sun.jvm.hotspot.tools.Tool 并实现一个 run() 方法,在该方法内使用SA的API访问JVM即可。
(或者更简单的:可以直接起CLHSDB,attach上之后运行下面这句就好
做的事情跟本文后面的例子一样用iteratePerm(),而下面这句
直接把PermGen内容输出到文件里去
)
这次我们就把一个跑在HotSpot上的Java进程的perm gen里所有对象的信息打到标准输出流上看看吧。
测试环境是32位Linux,x86,Sun JDK 6 update 2
(手边可用的JDK版本很多,随便拿了一个来用,呵呵 >_<)
代码如下:
很简单,假定目标Java进程用的是Parallel Scavenge(PS)算法的GC堆,输出GC堆的名字,当前perm gen的起始和结束地址,VM参数中设置的PermSize(perm gen的初始大小);然后是perm gen中所有对象的信息,包括对象摘要、地址、每个成员域的名字、偏移量和值等。
对HotSpot的VM参数不熟悉的同学可以留意一下几个参数在HotSpot源码中的定义:
要让SA连接到一个正在运行的Java进程最重要是提供进程ID。获取pid的方法有很多,今天演示的是利用OnOutOfMemoryError参数指定让HotSpot在遇到内存不足而抛出OutOfMemoryError时执行一段用户指定的命令;在这个命令中可以使用%p占位符表示pid,HotSpot在执行命令时会把真实pid填充进去。
然后来造一个引发OOM的导火索:
对32位HotSpot来说,main()方法里的new Long[256*1024*1024]会试图创建一个大于1GB的数组对象,那么只要把-Xmx参数设到1GB或更小便足以引发OOM了。
如何知道这个数组对象会占用超过1GB的呢?Long[]是一个引用类型的数组,只要知道32位HotSpot中采用的对象布局:
就知道一大半了~
跑一下Foo程序。留意到依赖SA的代码要编译的话需要$JAVA_HOME/lib/sa-jdi.jar在classpath上,执行时同理。指定GC算法为Parallel Scavenge,并指定Java堆(不包括perm gen)的初始和最大值都为1GB:
得到的foo.txt就是要演示的输出结果。把它压缩了放在附件里,有兴趣但懒得自己实验的同学也可以观摩一下~
在foo.txt的开头可以看到:
这里显示了GC堆确实是Parallel Scavenge的,其中perm gen当前的起始地址为0x70e60000,结束地址为0x71e60000,中间连续的虚拟内存空间都分配给perm gen使用。简单计算一下可知perm gen大小为16MB,与下面打出的PermSize参数的值完全吻合。
通过阅读该日志文件,可以得知HotSpot在perm gen里存放的对象主要有:
- Klass系对象
- java.lang.Class对象
- 字符串常量
- 符号(Symbol/symbolOop)常量
- 常量池对象
- 方法对象
等等,以及它们所直接依赖的一些对象。具体这些都是什么留待以后有空再写。
接下来挑几个例子来简单讲解一下如何阅读这个日志文件里的对象描述。
首先看一个String对象。先看看JDK里java.lang.String对象的声明是什么样的:
留意到String对象有4个成员域,分别是:
String类自身有三个静态变量,分别是:
回到我们的foo.txt日志文件来看一个String的对象实例:
这是在HotSpot的字符串池里的一个字符串常量对象,"main"。
日志中的“"main"”是对象的摘要,String对象有特别处理显示为它的内容,其它多数类型的对象都是显示类型名之类的。
在@符号之后的就是对象的起始地址,十六进制表示。
紧接着后面是对象占用GC堆的大小。很明显这个String对象自身占用了24字节。这里强调是“占用”的大小是因为对象除了存储必要的数据需要空间外,为了满足数据对齐的要求可能会有一部分空间作为填充数据而空占着。
String在内存中的布局是:
32位HotSpot上要求64位/8字节对齐,String占用的24字节正好全部都是有效数据,不需要填充空数据。
上面的String实例在内存中的实际数据如下:
OK,那我们来每个成员域都过一遍,看看有何玄机。
第一个是_mark。在HotSpot的C++代码里它的类型是markOop,在SA里以sun.jvm.hotspot.oops.Mark来表现。
它属于对象头(object header)的一部分,是个多用途标记,可用于记录GC的标记(mark)状态、锁状态、偏向锁(bias-locking)状态、身份哈希值(identity hash)缓存等等。它的可能组合包括:
例子中的"main"字符串的_mark值为1,也就是说它:
- 没有被锁住;
- 现在未被GC标记;
- 年龄为0(尚未经历过GC);
- 身份哈希值尚未被计算。
HotSpot的GC堆中许多创建没多久的对象的_mark值都会是1,属于正常现象。
接下来看SA输出的日志中写为_klass而在我的图示上写为_metadata的这个域。
在HotSpot的C++代码里,oopDesc是所有放在GC堆上的对象的顶层类,它的成员就构成了对象头。HotSpot在C++代码中用instanceOopDesc类来表示Java对象,而该类继承oopDesc,所以HotSpot中的Java对象也自然拥有oopDesc所声明的头部。
hotspot/src/share/vm/oops/oop.hpp:
_metadata与前面提过的_mark一同构成了对象头。
_metadata是个union,为了能兼容32位、64位与开了压缩指针(CompressedOops)等几种情况。无论是这个union中的_klass还是_compressed_klass域,它们都是用于指向一个描述该对象的klass对象的指针。SA的API屏蔽了普通指针与压缩指针之间的差异,所以就直接把_metadata._klass称为了_klass。
对象头的格式是固定的,而对象自身内容的布局则由HotSpot根据一定规则来决定。Java类在被HotSpot加载时,其对象实例的布局与类自身的布局都会被计算出来。这个计算规则有机会以后再详细写。
现在来看看"main"这个String对象实例自身的域都是些什么。
value:指向真正保存字符串内容的对象的引用。留意Java里String并不把真正的字符内容直接存在自己里面,而是引用一个char[]对象来承载真正的存储。
从Java一侧看value域的类型是char[],而从HotSpot的C++代码来看它就是个普通的指针而已。它当前值是0x7100b158,指向一个char[]对象的起始位置。
offset:字符串的内容从value指向的char[]中的第几个字符开始算(0-based)。int型,32位带符号整数,这从Java和C++来看都差不多。当前值为0。
count:该字符串的长度,或者说包含的UTF-16字符的个数。类型同上。当前值为4,说明该字符串有4个UTF-16字符。
hash:缓存该String对象的哈希值的成员域。类型同上。当前值为0,说明该实例的String.hashCode()方法尚未被调用过,因而尚未缓存住该字符串的哈希值。
String对象的成员域都走过一遍了,来看看value所指向的对象状况。
这就是"main"字符串的value所引用的char[]的日志。
[C 是char[]在JVM中的内部名称。
在@符号之后的0x7100b158是该对象的起始地址。
该对象占用GC堆的大小是24字节。留意了哦。
看看它的成员域。
_mark与_klass构成的对象头就不重复介绍了。可以留意的是元素类型为原始类型(boolean、char、short、int、long、float、double)的数组在HotSpot的C++代码里是用typeArrayOopDesc来表示的;这里的char[]也不例外。描述typeArrayOopDesc的klass对象是typeArrayKlass类型的,所以可以看到日志里_klass的值写着TypeArrayKlass for [C。
接下来是_length域。HotSpot中,数组对象比普通对象的头要多一个域,正是这个描述数组元素个数的_length。Java语言中数组的.length属性、JVM字节码中的arraylength要取的也正是这个值。
日志中的这个数组对象有4个字符,所以_length值为4。
再后面就是数组的内容了。于是该char[]在内存中的布局是:
Java的char是UTF-16字符,宽度是16位/2字节;4个字符需要8字节,加上对象头的4*3=12字节,总共需要20字节。但该char[]却占用了GC堆上的24字节,正是因为前面提到的数据对齐要求——HotSpot要求GC堆上的对象是8字节对齐的,20向上找最近的8的倍数就是24了。用于对齐的这部分会被填充为0。
"main"对象的value指向的char[]也介绍过了,回过头来看看它的_metadata._klass所指向的klass对象又是什么状况。
从HotSpot的角度来看,klass就是用于描述GC堆上的对象的对象;如果一个对象的大小、域的个数与类型等信息不固定的话,它就需要特定的klass对象来描述。
instanceOopDesc用于表示Java对象,instanceKlass用于描述它,但自身却又有些不固定的信息需要被描述,因而又有instanceKlassKlass;如此下去会没完没了,所以有个klassKlass作为这个描述链上的终结符。
klass的关系图:
(图片来源)
回到foo.txt日志文件上来,找到"main"对象的_klass域所引用的instanceKlass对象:
还记得上文提到过的String类的3个静态变量么?有没有觉得有什么眼熟的地方?
没错,在HotSpot中,Java类的静态变量就是作为该类对应的instanceKlass的实例变量出现的。上面的日志里最后三行描述了String的静态变量所在。
这是件非常自然的事:类用于描述对象,类自身也是对象,有用于描述自身的类;某个类的所谓“静态变量”就是该类对象的实例变量。很多对象系统都是这么设计的。HotSpot的这套oop体系(指“普通对象指针”,不是指“面向对象编程”)继承自Strongtalk,实际上反而比暴露给Java的对象模型显得更加面向对象一些。
HotSpot并不把instanceKlass暴露给Java,而会另外创建对应的java.lang.Class对象,并将后者称为前者的“Java镜像”,两者之间互相持有引用。日志中的_java_mirror便是该instanceKlass对Class对象的引用。
镜像机制被认为是良好的面向对象的反射与元编程设计的重要机制。Gilad Bracha与David Ungar还专门写了篇论文来阐述此观点,参考Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages。
顺带把"main"对象的_klass链上余下的两个对象的日志也贴出来:
所有instanceKlass对象都是被这个instanceKlassKlass对象所描述的。
而所有*KlassKlass对象都是被这个klassKlass对象所描述的。
klass对象的更详细的介绍也留待以后再写吧~至少得找时间写写instanceKlass与vtable、itable的故事。
嘛,今天的废话到此结束 ^_^
希望这帖能解答先前关于java里面的全局变量的内存分配一帖中的疑问。也希望大家能多多支持高级语言虚拟机圈子,有什么HLL VM相关的话题想讨论的欢迎来转转~
我理解错了。。。呵呵。
作者:RednaxelaFX -> rednaxelafx.iteye.com)
接着前天的与昨天的帖,今天也来介绍一个HotSpot的Serviceability Agent(以下简称SA)的玩法例子。
昨天用SA把x86机器码反汇编到汇编代码,或许对多数Java程序员来说并不怎么有趣。那么今天就来点更接近Java,但又经常被误解的话题——HotSpot的GC堆的permanent generation。
要用SA里最底层的API来连接上一个Java进程并不困难,不过SA还提供了更方便的封装:只要继承 sun.jvm.hotspot.tools.Tool 并实现一个 run() 方法,在该方法内使用SA的API访问JVM即可。
(或者更简单的:可以直接起CLHSDB,attach上之后运行下面这句就好
jseval "sa.objHeap.iteratePerm(new sapkg.oops.HeapPrinter(java.lang.System.out))"
做的事情跟本文后面的例子一样用iteratePerm(),而下面这句
jseval "io = java.io; sa.objHeap.iteratePerm(new sapkg.oops.HeapPrinter(new io.PrintStream(new io.FileOutputStream('perm.log'))))"
直接把PermGen内容输出到文件里去
)
这次我们就把一个跑在HotSpot上的Java进程的perm gen里所有对象的信息打到标准输出流上看看吧。
测试环境是32位Linux,x86,Sun JDK 6 update 2
(手边可用的JDK版本很多,随便拿了一个来用,呵呵 >_<)
代码如下:
import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSPermGen; import sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap; import sun.jvm.hotspot.gc_implementation.shared.MutableSpace; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.Universe; import sun.jvm.hotspot.oops.HeapPrinter; import sun.jvm.hotspot.oops.HeapVisitor; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.runtime.VM; import sun.jvm.hotspot.tools.Tool; /** * @author sajia * */ public class TestPrintPSPermGen extends Tool { public static void main(String[] args) { TestPrintPSPermGen test = new TestPrintPSPermGen(); test.start(args); test.stop(); } @Override public void run() { VM vm = VM.getVM(); Universe universe = vm.getUniverse(); CollectedHeap heap = universe.heap(); puts("GC heap name: " + heap.kind()); if (heap instanceof ParallelScavengeHeap) { ParallelScavengeHeap psHeap = (ParallelScavengeHeap) heap; PSPermGen perm = psHeap.permGen(); MutableSpace permObjSpace = perm.objectSpace(); puts("Perm gen: [" + permObjSpace.bottom() + ", " + permObjSpace.end() + ")"); long permSize = 0; for (VM.Flag f : VM.getVM().getCommandLineFlags()) { if ("PermSize".equals(f.getName())) { permSize = Long.parseLong(f.getValue()); break; } } puts("PermSize: " + permSize); } puts(); ObjectHeap objHeap = vm.getObjectHeap(); HeapVisitor heapVisitor = new HeapPrinter(System.out); objHeap.iteratePerm(heapVisitor); } private static void puts() { System.out.println(); } private static void puts(String s) { System.out.println(s); } }
很简单,假定目标Java进程用的是Parallel Scavenge(PS)算法的GC堆,输出GC堆的名字,当前perm gen的起始和结束地址,VM参数中设置的PermSize(perm gen的初始大小);然后是perm gen中所有对象的信息,包括对象摘要、地址、每个成员域的名字、偏移量和值等。
对HotSpot的VM参数不熟悉的同学可以留意一下几个参数在HotSpot源码中的定义:
product(ccstrlist, OnOutOfMemoryError, "", "Run user-defined commands on first java.lang.OutOfMemoryError") product(bool, UseParallelGC, false, "Use the Parallel Scavenge garbage collector") product_pd(uintx, PermSize, "Initial size of permanent generation (in bytes)") product_pd(uintx, MaxPermSize, "Maximum size of permanent generation (in bytes)")
要让SA连接到一个正在运行的Java进程最重要是提供进程ID。获取pid的方法有很多,今天演示的是利用OnOutOfMemoryError参数指定让HotSpot在遇到内存不足而抛出OutOfMemoryError时执行一段用户指定的命令;在这个命令中可以使用%p占位符表示pid,HotSpot在执行命令时会把真实pid填充进去。
然后来造一个引发OOM的导火索:
public class Foo { public static void main(String[] args) { Long[] array = new Long[256*1024*1024]; } }
对32位HotSpot来说,main()方法里的new Long[256*1024*1024]会试图创建一个大于1GB的数组对象,那么只要把-Xmx参数设到1GB或更小便足以引发OOM了。
如何知道这个数组对象会占用超过1GB的呢?Long[]是一个引用类型的数组,只要知道32位HotSpot中采用的对象布局:
----------------------- (+0) | _mark | ----------------------- (+4) | _metadata | ----------------------- (+8) | 数组长度 length | ----------------------- (+12+4*0) | 下标为0的元素 | ----------------------- (+12+4*1) | 下标为1的元素 | ----------------------- | ... | ----------------------- (+12+4*n) | 下标为n的元素 | ----------------------- | ... | -----------------------
就知道一大半了~
跑一下Foo程序。留意到依赖SA的代码要编译的话需要$JAVA_HOME/lib/sa-jdi.jar在classpath上,执行时同理。指定GC算法为Parallel Scavenge,并指定Java堆(不包括perm gen)的初始和最大值都为1GB:
[sajia@sajia ~]$ java -server -version java version "1.6.0_02" Java(TM) SE Runtime Environment (build 1.6.0_02-b05) Java HotSpot(TM) Server VM (build 1.6.0_02-b05, mixed mode) [sajia@sajia ~]$ javac Foo.java [sajia@sajia ~]$ javac -classpath ".:$JAVA_HOME/lib/sa-jdi.jar" TestPrintPSPermGen.java [sajia@sajia ~]$ java -server -XX:+UseParallelGC -XX:OnOutOfMemoryError='java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen %p > foo.txt' -Xms1g -Xmx1g Foo # # java.lang.OutOfMemoryError: Java heap space # -XX:OnOutOfMemoryError="java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen %p > foo.txt" # Executing /bin/sh -c "java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen 23373 > foo.txt"... Attaching to process ID 23373, please wait... Debugger attached successfully. Server compiler detected. JVM version is 1.6.0_02-b05 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at Foo.main(Foo.java:5) [sajia@sajia ~]$
得到的foo.txt就是要演示的输出结果。把它压缩了放在附件里,有兴趣但懒得自己实验的同学也可以观摩一下~
在foo.txt的开头可以看到:
GC heap name: ParallelScavengeHeap Perm gen: [0x70e60000, 0x71e60000) PermSize: 16777216
这里显示了GC堆确实是Parallel Scavenge的,其中perm gen当前的起始地址为0x70e60000,结束地址为0x71e60000,中间连续的虚拟内存空间都分配给perm gen使用。简单计算一下可知perm gen大小为16MB,与下面打出的PermSize参数的值完全吻合。
通过阅读该日志文件,可以得知HotSpot在perm gen里存放的对象主要有:
- Klass系对象
- java.lang.Class对象
- 字符串常量
- 符号(Symbol/symbolOop)常量
- 常量池对象
- 方法对象
等等,以及它们所直接依赖的一些对象。具体这些都是什么留待以后有空再写。
接下来挑几个例子来简单讲解一下如何阅读这个日志文件里的对象描述。
首先看一个String对象。先看看JDK里java.lang.String对象的声明是什么样的:
package java.lang; // imports ... public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { // ... } }
留意到String对象有4个成员域,分别是:
名字 | 类型 | 引用类型还是值类型 |
value | char[] | 引用类型 |
offset | int | 值类型 |
count | int | 值类型 |
hash | int | 值类型 |
String类自身有三个静态变量,分别是:
名字 | 类型 | 引用类型还是值类型 | 备注 |
serialVersionUID | long | 值类型 | 常量 |
serialPersistentFields | java.io.ObjectStreamField[] | 引用类型 | 只读变量 |
CASE_INSENSITIVE_ORDER | java.lang.String.CaseInsensitiveComparator | 引用类型 | 只读变量 |
回到我们的foo.txt日志文件来看一个String的对象实例:
"main" @ 0x7100b140 (object size = 24) - _mark: {0} :1 - _klass: {4} :InstanceKlass for java/lang/String @ 0x70e6c6a0 - value: {8} :[C @ 0x7100b158 - offset: {12} :0 - count: {16} :4 - hash: {20} :0
这是在HotSpot的字符串池里的一个字符串常量对象,"main"。
日志中的“"main"”是对象的摘要,String对象有特别处理显示为它的内容,其它多数类型的对象都是显示类型名之类的。
在@符号之后的就是对象的起始地址,十六进制表示。
紧接着后面是对象占用GC堆的大小。很明显这个String对象自身占用了24字节。这里强调是“占用”的大小是因为对象除了存储必要的数据需要空间外,为了满足数据对齐的要求可能会有一部分空间作为填充数据而空占着。
String在内存中的布局是:
----------------------- (+0) | _mark | ----------------------- (+4) | _metadata | ----------------------- (+8) | value | ----------------------- (+12)| offset | ----------------------- (+16)| count | ----------------------- (+20)| hash | -----------------------
32位HotSpot上要求64位/8字节对齐,String占用的24字节正好全部都是有效数据,不需要填充空数据。
上面的String实例在内存中的实际数据如下:
偏移量(字节) | 数值(二进制表示) | 数值(十六进制表示) | 宽度(位/字节) |
+0 | 00000000000000000000000000000001 | 00000001 | 32位/4字节 |
+4 | 01110000111001101100011010100000 | 70e6c6a0 | 32位/4字节 |
+8 | 01110001000000001011000101011000 | 7100b158 | 32位/4字节 |
+12 | 00000000000000000000000000000000 | 00000000 | 32位/4字节 |
+16 | 00000000000000000000000000000100 | 00000004 | 32位/4字节 |
+20 | 00000000000000000000000000000000 | 00000000 | 32位/4字节 |
OK,那我们来每个成员域都过一遍,看看有何玄机。
第一个是_mark。在HotSpot的C++代码里它的类型是markOop,在SA里以sun.jvm.hotspot.oops.Mark来表现。
它属于对象头(object header)的一部分,是个多用途标记,可用于记录GC的标记(mark)状态、锁状态、偏向锁(bias-locking)状态、身份哈希值(identity hash)缓存等等。它的可能组合包括:
比特域(名字或常量值:位数) | 标识(tag) | 状态 | |
身份哈希值:25, 年龄:4, 0:1 | 01 | 未锁 | |
锁记录地址:30 | 00 | 被轻量级锁住 | |
monitor对象地址:30 | 10 | 被重量级锁住 | |
转向地址:30 | 11 | 被GC标记 | |
线程ID:23, 纪元:2, 年龄:4, 1:1 | 01 | 被偏向锁住/可被偏向锁 |
例子中的"main"字符串的_mark值为1,也就是说它:
- 没有被锁住;
- 现在未被GC标记;
- 年龄为0(尚未经历过GC);
- 身份哈希值尚未被计算。
HotSpot的GC堆中许多创建没多久的对象的_mark值都会是1,属于正常现象。
接下来看SA输出的日志中写为_klass而在我的图示上写为_metadata的这个域。
在HotSpot的C++代码里,oopDesc是所有放在GC堆上的对象的顶层类,它的成员就构成了对象头。HotSpot在C++代码中用instanceOopDesc类来表示Java对象,而该类继承oopDesc,所以HotSpot中的Java对象也自然拥有oopDesc所声明的头部。
hotspot/src/share/vm/oops/oop.hpp:
class oopDesc { private: volatile markOop _mark; union _metadata { wideKlassOop _klass; narrowOop _compressed_klass; } _metadata; };
_metadata与前面提过的_mark一同构成了对象头。
_metadata是个union,为了能兼容32位、64位与开了压缩指针(CompressedOops)等几种情况。无论是这个union中的_klass还是_compressed_klass域,它们都是用于指向一个描述该对象的klass对象的指针。SA的API屏蔽了普通指针与压缩指针之间的差异,所以就直接把_metadata._klass称为了_klass。
对象头的格式是固定的,而对象自身内容的布局则由HotSpot根据一定规则来决定。Java类在被HotSpot加载时,其对象实例的布局与类自身的布局都会被计算出来。这个计算规则有机会以后再详细写。
现在来看看"main"这个String对象实例自身的域都是些什么。
value:指向真正保存字符串内容的对象的引用。留意Java里String并不把真正的字符内容直接存在自己里面,而是引用一个char[]对象来承载真正的存储。
从Java一侧看value域的类型是char[],而从HotSpot的C++代码来看它就是个普通的指针而已。它当前值是0x7100b158,指向一个char[]对象的起始位置。
offset:字符串的内容从value指向的char[]中的第几个字符开始算(0-based)。int型,32位带符号整数,这从Java和C++来看都差不多。当前值为0。
count:该字符串的长度,或者说包含的UTF-16字符的个数。类型同上。当前值为4,说明该字符串有4个UTF-16字符。
hash:缓存该String对象的哈希值的成员域。类型同上。当前值为0,说明该实例的String.hashCode()方法尚未被调用过,因而尚未缓存住该字符串的哈希值。
String对象的成员域都走过一遍了,来看看value所指向的对象状况。
[C @ 0x7100b158 (object size = 24) - _mark: {0} :1 - _klass: {4} :TypeArrayKlass for [C @ 0x70e60440 - _length: {8} :4 - 0: {12} :m - 1: {14} :a - 2: {16} :i - 3: {18} :n
这就是"main"字符串的value所引用的char[]的日志。
[C 是char[]在JVM中的内部名称。
在@符号之后的0x7100b158是该对象的起始地址。
该对象占用GC堆的大小是24字节。留意了哦。
看看它的成员域。
_mark与_klass构成的对象头就不重复介绍了。可以留意的是元素类型为原始类型(boolean、char、short、int、long、float、double)的数组在HotSpot的C++代码里是用typeArrayOopDesc来表示的;这里的char[]也不例外。描述typeArrayOopDesc的klass对象是typeArrayKlass类型的,所以可以看到日志里_klass的值写着TypeArrayKlass for [C。
接下来是_length域。HotSpot中,数组对象比普通对象的头要多一个域,正是这个描述数组元素个数的_length。Java语言中数组的.length属性、JVM字节码中的arraylength要取的也正是这个值。
日志中的这个数组对象有4个字符,所以_length值为4。
再后面就是数组的内容了。于是该char[]在内存中的布局是:
----------------------- (+0) | _mark | ----------------------- (+4) | _metadata | ----------------------- (+8) | 数组长度 length | ----------------------- (+12) | char[0] | char[1] | ----------------------- (+16) | char[2] | char[3] | ----------------------- (+20) | 填充0 | -----------------------
Java的char是UTF-16字符,宽度是16位/2字节;4个字符需要8字节,加上对象头的4*3=12字节,总共需要20字节。但该char[]却占用了GC堆上的24字节,正是因为前面提到的数据对齐要求——HotSpot要求GC堆上的对象是8字节对齐的,20向上找最近的8的倍数就是24了。用于对齐的这部分会被填充为0。
"main"对象的value指向的char[]也介绍过了,回过头来看看它的_metadata._klass所指向的klass对象又是什么状况。
从HotSpot的角度来看,klass就是用于描述GC堆上的对象的对象;如果一个对象的大小、域的个数与类型等信息不固定的话,它就需要特定的klass对象来描述。
instanceOopDesc用于表示Java对象,instanceKlass用于描述它,但自身却又有些不固定的信息需要被描述,因而又有instanceKlassKlass;如此下去会没完没了,所以有个klassKlass作为这个描述链上的终结符。
klass的关系图:
(图片来源)
回到foo.txt日志文件上来,找到"main"对象的_klass域所引用的instanceKlass对象:
InstanceKlass for java/lang/String @ 0x70e6c6a0 (object size = 384) - _mark: {0} :1 - _klass: {4} :InstanceKlassKlass @ 0x70e60168 - _java_mirror: {60} :Oop for java/lang/Class @ 0x70e77760 - _super: {64} :InstanceKlass for java/lang/Object @ 0x70e65af8 - _size_helper: {12} :6 - _name: {68} :#java/lang/String @ 0x70e613e8 - _access_flags: {84} :134217777 - _subklass: {72} :null - _next_sibling: {76} :InstanceKlass for java/lang/CharSequence @ 0x70e680e8 - _alloc_count: {88} :0 - _array_klasses: {112} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x70ef6298 - _methods: {116} :ObjArray @ 0x70e682a0 - _method_ordering: {120} :[I @ 0x70e61330 - _local_interfaces: {124} :ObjArray @ 0x70e67998 - _transitive_interfaces: {128} :ObjArray @ 0x70e67998 - _nof_implementors: {268} :0 - _implementors[0]: {164} :null - _implementors[0]: {168} :null - _fields: {132} :[S @ 0x70e68230 - _constants: {136} :ConstantPool for java/lang/String @ 0x70e65c38 - _class_loader: {140} :null - _protection_domain: {144} :null - _signers: {148} :null - _source_file_name: {152} :#String.java @ 0x70e67980 - _inner_classes: {160} :[S @ 0x70e6c820 - _nonstatic_field_size: {196} :4 - _static_field_size: {200} :4 - _static_oop_field_size: {204} :2 - _nonstatic_oop_map_size: {208} :1 - _is_marked_dependent: {212} :0 - _init_state: {220} :5 - _vtable_len: {228} :5 - _itable_len: {232} :9 - serialVersionUID: {368} :-6849794470754667710 - serialPersistentFields: {360} :ObjArray @ 0x74e882c8 - CASE_INSENSITIVE_ORDER: {364} :Oop for java/lang/String$CaseInsensitiveComparator @ 0x74e882c0
还记得上文提到过的String类的3个静态变量么?有没有觉得有什么眼熟的地方?
没错,在HotSpot中,Java类的静态变量就是作为该类对应的instanceKlass的实例变量出现的。上面的日志里最后三行描述了String的静态变量所在。
这是件非常自然的事:类用于描述对象,类自身也是对象,有用于描述自身的类;某个类的所谓“静态变量”就是该类对象的实例变量。很多对象系统都是这么设计的。HotSpot的这套oop体系(指“普通对象指针”,不是指“面向对象编程”)继承自Strongtalk,实际上反而比暴露给Java的对象模型显得更加面向对象一些。
HotSpot并不把instanceKlass暴露给Java,而会另外创建对应的java.lang.Class对象,并将后者称为前者的“Java镜像”,两者之间互相持有引用。日志中的_java_mirror便是该instanceKlass对Class对象的引用。
镜像机制被认为是良好的面向对象的反射与元编程设计的重要机制。Gilad Bracha与David Ungar还专门写了篇论文来阐述此观点,参考Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages。
顺带把"main"对象的_klass链上余下的两个对象的日志也贴出来:
InstanceKlassKlass @ 0x70e60168 (object size = 120) - _mark: {0} :1 - _klass: {4} :KlassKlass @ 0x70e60000 - _java_mirror: {60} :Oop for java/lang/Class @ 0x70e76f20 - _super: {64} :null - _size_helper: {12} :0 - _name: {68} :null - _access_flags: {84} :0 - _subklass: {72} :null - _next_sibling: {76} :null - _alloc_count: {88} :0
所有instanceKlass对象都是被这个instanceKlassKlass对象所描述的。
KlassKlass @ 0x70e60000 (object size = 120) - _mark: {0} :1 - _klass: {4} :KlassKlass @ 0x70e60000 - _java_mirror: {60} :Oop for java/lang/Class @ 0x70e76e00 - _super: {64} :null - _size_helper: {12} :0 - _name: {68} :null - _access_flags: {84} :0 - _subklass: {72} :null - _next_sibling: {76} :null - _alloc_count: {88} :0
而所有*KlassKlass对象都是被这个klassKlass对象所描述的。
klass对象的更详细的介绍也留待以后再写吧~至少得找时间写写instanceKlass与vtable、itable的故事。
嘛,今天的废话到此结束 ^_^
希望这帖能解答先前关于java里面的全局变量的内存分配一帖中的疑问。也希望大家能多多支持高级语言虚拟机圈子,有什么HLL VM相关的话题想讨论的欢迎来转转~
评论
24 楼
99138851
2018-01-24
对象的大小为什么不固定,还有域的个数与类型等信息不固定。是为啥,这点好困惑,要不然就无法理解 InstanceKlassKlass存在的意义了。
23 楼
abcdefghiijklmnopqrs
2016-11-03
windows下的命令为
java -server -XX:+UseParallelGC -XX:OnOutOfMemoryError="java -classpath %JAVA_HOME%/lib/sa-jdi.jar;. TestPrintPSPermGen %p" > foo.txt -Xms1g -Xmx1g Foo
java -server -XX:+UseParallelGC -XX:OnOutOfMemoryError="java -classpath %JAVA_HOME%/lib/sa-jdi.jar;. TestPrintPSPermGen %p" > foo.txt -Xms1g -Xmx1g Foo
22 楼
danylolivia
2016-10-02
很有用,点赞
21 楼
妖人不要跑
2016-01-24
哥们写的很好,转走了 。。
20 楼
cocalmini
2015-11-05
R大 最近对字符串池感兴趣 终于索引到这个文章~。虽然不是全部能读懂但是也解释了多年心中的疑惑了~。准备动手实践下R大这个实验发现了进入了怪圈 - -:
1. 我现在在window开发用的是1.6JDK 没有sa的jar包!
2. 然后我用了1.8的JDK来跑,发现已经没有 sun.jvm.hotspot.gc_implementation.parallelScavenge.PSPermGen 这个类了!!
好像是 http://rednaxelafx.iteye.com/blog/905273Oracle HotSpot VM计划移除PermGen 有关.....
anyway 还是感谢R大~~~
1. 我现在在window开发用的是1.6JDK 没有sa的jar包!
2. 然后我用了1.8的JDK来跑,发现已经没有 sun.jvm.hotspot.gc_implementation.parallelScavenge.PSPermGen 这个类了!!
好像是 http://rednaxelafx.iteye.com/blog/905273Oracle HotSpot VM计划移除PermGen 有关.....
anyway 还是感谢R大~~~
19 楼
jjzhk
2014-12-15
jjzhk 写道
HotSpot并不把instanceKlass暴露给Java,而会另外创建对应的java.lang.Class对象,并将后者称为前者的“Java镜像”,两者之间互相持有引用。
这句中的java.lang.Class对象是否应该是java.lang.String对象?
谢谢R大神。
这句中的java.lang.Class对象是否应该是java.lang.String对象?
谢谢R大神。
我理解错了。。。呵呵。
18 楼
jjzhk
2014-12-15
HotSpot并不把instanceKlass暴露给Java,而会另外创建对应的java.lang.Class对象,并将后者称为前者的“Java镜像”,两者之间互相持有引用。
这句中的java.lang.Class对象是否应该是java.lang.String对象?
谢谢R大神。
这句中的java.lang.Class对象是否应该是java.lang.String对象?
谢谢R大神。
17 楼
lucky_huhu
2014-01-21
期待R大“找时间写写instanceKlass与vtable、itable的故事”
16 楼
zuoyefeng1
2012-09-14
话说要再看一遍编译原理的兄弟,这篇文章里说的与编译原理有毛关系,看来是没有学过编译原理的,或者是编译原理课逃课,连编译原理课本目录都没看过的。
15 楼
kingkan
2011-12-20
呵呵,撒迦的这篇文章,对我帮助很大,解开了我的一些疑虑哈,藏了。--虚空
14 楼
coffeesweet
2010-08-09
我要是有时间天天研究这些东西肯定也挺爽,羡慕楼主......
13 楼
sulifeng
2010-08-09
牛人啊大神啊..................
12 楼
lianj_lee
2010-08-06
确实有点入魔了.
说实话, 很多东西都到了底层!
说实话, 很多东西都到了底层!
11 楼
gstarwd
2010-08-06
哇 大爱~~
10 楼
divenLai
2010-08-06
很好,很强大
9 楼
AlwenS
2010-08-06
看完鸟,楼主的动手能力真强。
我也喜欢探究这些东西,只是功力还不如楼主深。
其实这东东和编译原理倒没多大关系了,主要看的是具体OO语言的Runtime Env模型。
比如c++就是没有penmgen的,所以他的动态能力和RTTI都非常弱。
我也喜欢探究这些东西,只是功力还不如楼主深。
其实这东东和编译原理倒没多大关系了,主要看的是具体OO语言的Runtime Env模型。
比如c++就是没有penmgen的,所以他的动态能力和RTTI都非常弱。
8 楼
seraphim871211
2010-08-06
“大叔”,你最近的帖子都看不下去了。。。
7 楼
hymanyung
2010-08-06
看得頭暈了.
6 楼
liujun999999
2010-08-06
晕,说实话,我没有看懂,看出差距来了,唉
5 楼
kaka2008
2010-08-06
fx同学出手即精华
而且帖子太深奥。。。
而且帖子太深奥。。。
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16276以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10453先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22395(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21874之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ... -
《深入理解Java虚拟机(第二版)》书评
2013-07-08 19:19 0值得推荐的中文Java虚拟机入门书 感谢作者赠与的样书,以下 ... -
豆列:从表到里学习JVM实现
2013-06-13 14:13 48365刚写了个学习JVM用的豆列跟大家分享。 豆列地址:http: ...
相关推荐
在Java 8之前,PermGen space 是HotSpot JVM的一部分,并且它的大小可以通过JVM参数进行调整。 当PermGen space的可用空间被耗尽时,Java应用程序将抛出`java.lang.OutOfMemoryError: PermGen space`异常。这种错误...
PermGen space,全称为Permanent Generation space,是早期Java HotSpot虚拟机中的一个内存区域,主要用来存放类和元数据。然而,这个区域经常因为类加载器的内存泄漏或频繁的类加载而导致`java.lang....
总的来说,HSDB是HotSpot VM的一个强大工具,它提供了丰富的功能来探索和理解JVM的运行时数据,包括内存布局、对象状态以及垃圾收集行为等。熟练掌握HSDB的使用,将极大地提升开发者对Java应用性能调优的能力。
- **反射数据也是对象**:使用对象来表示反射数据,如类、字段和方法等。 - **本地线程支持**:包括抢占式调度和多线程处理。 ##### 2.3 垃圾收集 HotSpot 虚拟机采用了多种垃圾收集算法: - **分代拷贝垃圾收集*...
根据提供的文件信息,“HotSpot实战[完整版][带书签].pdf”这本书主要围绕HotSpot虚拟机进行深入探讨,HotSpot作为Java虚拟机的一种实现,是当前最广泛使用的JVM之一,尤其在企业级应用中占据重要地位。下面将根据...
Hotspot是Oracle JDK和OpenJDK中的一个JVM实现,以其高效性能和优化能力而著称。本文将深入探讨OpenJDK中的JVM Hotspot实现源码,帮助读者理解其内部机制和优化策略。 首先,Hotspot JVM的核心设计理念是“热Spot”...
《HotSpot虚拟机对象探秘》是一份详细探讨Java虚拟机内部对象创建、内存布局以及访问定位的资源。这份资料采用Xmind脑图的形式,旨在帮助读者深入理解JVM的底层原理,尤其聚焦于HotSpot虚拟机。内容涵盖对象实例化、...
在64位JVM上,由于对象头的大小不同(HotSpot VM的CompressedOops模式下,对象头可能为8字节或16字节),对象的实际大小可能会有所不同。此外,如果开启了引用压缩,对象引用可能会占用更少的空间。 在进行性能优化...
JDK7作为Java发展历程中的一个重要版本,引入了许多创新特性,而理解其底层C++源码和HotSpot虚拟机源码对于提升开发水平和优化性能具有重大意义。 首先,JDK7源码是Java Development Kit的基石,包含了Java语言的...
1. 解释器:Hotspot VM最初使用解释器来执行字节码,这是一种快速启动但执行速度相对较慢的方式。 2. 编译器:随着程序的运行,Hotspot VM会识别出执行频率高的“热点”代码,并使用Just-In-Time (JIT) 编译器将其...
【标题】"hotspot-8.rar" 涉及的核心知识点是HotSpot虚拟机和JVM(Java Virtual Machine)的学习,这是一款由Oracle公司开发的Java虚拟机实现,广泛应用于Java程序的运行与优化。HotSpot是Java平台的重要组成部分,...
6. **性能监控和调试工具**:书中可能包含如何使用JVisualVM、JConsole、JFR(Java Flight Recorder)和JMC(Java Mission Control)等工具来监控和诊断HotSpot虚拟机的状态。 7. **并行与并发**:HotSpot虚拟机...
Hotspot虚拟机,作为Java平台上的核心组成部分,一直以来都是Java性能优化的重要研究对象。本资源"hotspot-d9c3790c85c1.rar"包含了Hotspot 1.6版本的源代码,为深入理解JVM的工作原理提供了宝贵的材料。通过对这些...
当对象被创建时,HotSpot会默认给锁对象添加一个偏向版本的标识。当线程首次通过CAS操作(比较并交换)获取锁时,若成功,就会在对象头中记录当前线程的ID,表明这个锁已被“偏向”给这个线程。 3. **偏见锁的重...
Tracing GC是一种常见的垃圾收集策略,它通过跟踪从根集合出发可达的对象来确定哪些对象是活的。在三色遍历中,对象的状态被标记为白色(未访问)、灰色(已访问但未处理)和黑色(已完全处理)。在遍历过程中,首先...
总的来说,JDK 1.8是一个具有里程碑意义的版本,它的特性如Lambda表达式、Stream API、日期和时间API的改进以及HotSpot虚拟机的优化,都极大地推动了Java语言的发展,提升了开发者的生产力。对于Java开发者来说,...
综上所述,Hotspot 作为一种先进的综合设计思想,不仅在传统的半导体行业中发挥着重要作用,在新兴的技术领域也展现出了广阔的应用前景。通过对功率能量和温度的精确模拟,它为电子产品的设计带来了前所未有的便利性...
本书深入浅出地讲解了 HotSpot 虚拟机的工作原理,将隐藏在它内部的本质内容逐一呈现在读者面前,包 括 OpenJDK 与 HotSpot 项目、编译和调试 HotSpot 的方法、HotSpot 内核结构、Launcher、OOP-Klass 对象表 示系统...
Hotspot是Oracle JDK中的一个关键组件,它是一款高效的Java虚拟机(JVM)。JDK8u-hotspot源码提供了对Hotspot内部工作原理的深入了解,这对于Java开发者优化性能、调试问题以及理解JVM内部机制至关重要。Hotspot的...
《深入剖析HotSpot虚拟机源码》 HotSpot VM,作为Java开发人员耳熟能详的名字,是Sun JDK和OpenJDK中的核心组件,它的普及程度无出其右...因此,掌握HotSpot源码,对于每一个Java开发者来说,都是一项重要的技能提升。