房山区做网站,网站投票链接怎么做的,东营建设工程信息网站,织梦模板网站源码下载参考链接#xff1a; 了解Java中的类和对象
今天看到一个不错的PPT#xff1a;Build Memory-efficient Java Applications#xff0c;开篇便提出了一个问题#xff0c;在Hotspot JVM中#xff0c;32位机器下#xff0c;Integer对象的大小是int的几倍#xff1f;
我们…参考链接 了解Java中的类和对象
今天看到一个不错的PPTBuild Memory-efficient Java Applications开篇便提出了一个问题在Hotspot JVM中32位机器下Integer对象的大小是int的几倍
我们都知道在Java语言规范已经规定了int的大小是4个字节那么Integer对象的大小是多少呢要知道一个对象的大小那么必须需要知道对象在虚拟机中的结构是怎样的来看看Hotspot中对象在内存中的结构 从上面的这张图里面可以看出对象在内存中的结构主要包含以下几个部分
Mark Word对象的Mark Word部分占4个字节其内容是一系列的标记位比如轻量级锁的标记位偏向锁标记位等等。Class对象指针Class对象指针的大小也是4个字节其指向的位置是对象对应的Class对象其对应的元数据对象的内存地址对象实际数据这里面包括了对象的所有成员变量其大小由各个成员变量的大小决定比如byte和boolean是1个字节short和char是2个字节int和float是4个字节long和double是8个字节reference是4个字节对齐最后一部分是对齐填充的字节按8个字节填充。
根据上面的图那么我们可以得出Integer的对象的结构如下 Integer只有一个int类型的成员变量value所以其对象实际数据部分的大小是4个字节然后再在后面填充4个字节达到8字节的对齐所以可以得出Integer对象的大小是16个字节。
因此我们可以得出Integer对象的大小是原生的int类型的4倍。
关于对象的内存结构需要注意数组的内存结构和普通对象的内存结构稍微不同因为数据有一个长度length字段所以在对象头后面还多了一个int类型的length字段占4个字节接下来才是数组中的数据如下图 关于对象内存布局更多的内容可以看这篇文章Java Objects Memory Structure 【转】HotSpot虚拟机对象探秘
0人收藏此文章, 我要收藏 发表于8个月前(2012-02-19 23:29) , 已有44次阅读 共0个评论 请读者首先注意本篇的题目中的限定语“HotSpot虚拟机”在虚拟机规范中明确写道 所有在虚拟机规范之中没有明确描述的实现细节都不应成为虚拟机设计者发挥创造性的牵绊设计者可以完全自主决定所有规范中不曾描述的虚拟机内部细节。 例 如运行时数据区的内存如何布局、选用哪种垃圾收集的算法等”。因此本篇整个内存篇中所有的文章的内容会涉及到虚拟机“自主决定”的实现我们的讨 论将在HotSpot VM的范围内展开。同时我也假定读者已经理解了虚拟机规范中所定义的JVM公共内存模型例如运行时数据区域、栈帧结构等基础知识如果读者对这些内容 有疑问可以先阅读《Java虚拟机规范JavaSE 7 Editon》[注1]第2章或《深入理解Java虚拟机JVM高级特性与最佳实践》[注2]的第2、3章相关内容。 对象的创建 Java是一门面向对象的编程语言Java程序运行过程中无时无刻都有对象被创建出来。在语言层面上创建对象通常例外克隆、反序列化仅仅是一个 new关键字而已而在虚拟机中对象本文中讨论的对象限于普通Java对象不包括数组和Class对象等的创建又是怎样一个过程呢 虚拟机遇到一条new指令时首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用并且检查这个符号引用代表的类是否已被加载、解析和初始化过的。如果没有那必须先执行相应的类加载过程。 在类加载检查通过后接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定为对象分配空间的任务具体便等同于一块确定大小 的内存从Java堆中划分出来怎么划呢假设Java堆中内存是绝对规整的所有用过的内存都被放在一边空闲的内存被放在另一边中间放着一个指针作 为分界点的指示器那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离这种分配方式称为“指针碰撞”Bump The Pointer。如果Java堆中的内存并不是规整的已被使用的内存和空闲的内存相互交错那就没有办法简单的进行指针碰撞了虚拟机就必须维护一个列表记录上哪些内存块是可用的在分配的时候从列表中找到一块足够大的空间划分给对象实例并更新列表上的记录这种分配方式称为“空闲列表”Free List。选择哪种分配方式由Java堆是否规整决定而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因 此在使用Serial、ParNew等带Compact过程的收集器时系统采用的分配算法是指针碰撞而使用CMS这种基于Mark-Sweep算法的 收集器时说明一下CMS收集器可以通过UseCMSCompactAtFullCollection或 CMSFullGCsBeforeCompaction来整理内存就通常采用空闲列表。 除如何划分可用空间之外还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为即使是仅仅修改一个指针所指向的位置在并发情况下也并不是 线程安全的可能出现正在给对象A分配内存指针还没来得及修改对象B又同时使用了原来的指针来分配内存。解决这个问题有两个方案一种是对分配内存空 间的动作进行同步——实际上虚拟机是采用CAS配上失败重试的方式保证更新操作的原子性另外一种是把内存分配的动作按照线程划分在不同的空间之中进行 即每个线程在Java堆中预先分配一小块内存称为本地线程分配缓冲区TLAB Thread Local Allocation Buffer哪个线程要分配内存就在哪个线程的TLAB上分配只有TLAB用完分配新的TLAB时才需要同步锁定。虚拟机是否使用TLAB可以通过-XX:/-UseTLAB参数来设定。 内存分配完成之后虚拟机需要将分配到的内存空间都初始化为零值不包括对象头如果使用TLAB的话这一个工作也可以提前至TLAB分配时进行。这 步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用程序能访问到这些字段的数据类型所对应的零值。 接下来虚拟机要对对象进行必要的设置例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存 放在对象的对象头Object Header之中。根据虚拟机当前的运行状态的不同如是否启用偏向锁等对象头会有不同的设置方式。 在上面工作都完成之后在虚拟机的视角来看一个新的对象已经产生了。但是在Java程序的视角看来对象创建才刚刚开始——init方 法还没有执行所有的字段都为零值。因此一般来说由字节码中是否跟随有invokespecial指令所决定new指令之后会接着就是执 行init方法把对象按照程序员的意愿进行初始化这样一个真正可用的对象才算完全创建出来。 下面代码是HotSpot虚拟机bytecodeInterpreter.cpp中的代码片段这个解释器实现很少机会实际使用大部分平台上都使用模板 解释器当代码通过JIT编译器执行时差异就更大了。不过这段代码用于了解HotSpot的运作过程是没有什么问题的。
01// 确保常量池中存放的是已解释的类
02if (!constants-tag_at(index).is_unresolved_klass()) {
03 // 断言确保是klassOop和instanceKlassOop这部分下一节介绍
04 oop entry (klassOop) *constants-obj_at_addr(index);
05 assert(entry-is_klass(), Should be resolved klass);
06 klassOop k_entry (klassOop) entry;
07 assert(k_entry-klass_part()-oop_is_instance(), Should be instanceKlass);
08 instanceKlass* ik (instanceKlass*) k_entry-klass_part();
09 // 确保对象所属类型已经经过初始化阶段
10 if ( ik-is_initialized() ik-can_be_fastpath_allocated() ) {
11 // 取对象长度
12 size_t obj_size ik-size_helper();
13 oop result NULL;
14 // 记录是否需要将对象所有字段置零值
15 bool need_zero !ZeroTLAB;
16 // 是否在TLAB中分配对象
17 if (UseTLAB) {
18 result (oop) THREAD-tlab().allocate(obj_size);
19 }
20 if (result NULL) {
21 need_zero true;
22 // 直接在eden中分配对象
23retry:
24 HeapWord* compare_to *Universe::heap()-top_addr();
25 HeapWord* new_top compare_to obj_size;
26 // cmpxchg是x86中的CAS指令这里是一个C方法通过CAS方式分配空间并发失败的话转到retry中重试直至成功分配为止
27 if (new_top *Universe::heap()-end_addr()) {
28 if (Atomic::cmpxchg_ptr(new_top, Universe::heap()-top_addr(), compare_to) ! compare_to) {
29 goto retry;
30 }
31 result (oop) compare_to;
32 }
33 }
34 if (result ! NULL) {
35 // 如果需要为对象初始化零值
36 if (need_zero ) {
37 HeapWord* to_zero (HeapWord*) result sizeof(oopDesc) / oopSize;
38 obj_size - sizeof(oopDesc) / oopSize;
39 if (obj_size 0 ) {
40 memset(to_zero, 0, obj_size * HeapWordSize);
41 }
42 }
43 // 根据是否启用偏向锁设置对象头信息
44 if (UseBiasedLocking) {
45 result-set_mark(ik-prototype_header());
46 } else {
47 result-set_mark(markOopDesc::prototype());
48 }
49 result-set_klass_gap(0);
50 result-set_klass(k_entry);
51 // 将对象引用入栈继续执行下一条指令
52 SET_STACK_OBJECT(result, 0);
53 UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
54 }
55 }
56} 对象的内存布局 HotSpot虚拟机中对象在内存中存储的布局可以分为三块区域对象头Header、实例数据Instance Data和对齐填充Padding。 HotSpot虚拟机的对象头包括两部分信息第一部分用于存储对象自身的运行时数据 如哈希码HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等这部分数据的长度在32位和64位的虚拟机暂 不考虑开启压缩指针的场景中分别为32个和64个Bits官方称它为“Mark Word”。对象需要存储的运行时数据很多其实已经超出了32、64位Bitmap结构所能记录的限度但是对象头信息是与对象自身定义的数据无关的额 外存储成本考虑到虚拟机的空间效率Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机 中对象未被锁定的状态下Mark Word的32个Bits空间中的25Bits用于存储对象哈希码HashCode4Bits用于存储对象分代年龄2Bits用于存储锁标志 位1Bit固定为0在其他状态轻量级锁定、重量级锁定、GC标记、可偏向下对象的存储内容如下表所示。 表1 HotSpot虚拟机对象头Mark Word
存储内容标志位状态对象哈希码、对象分代年龄01未锁定指向锁记录的指针00轻量级锁定指向重量级锁的指针10膨胀重量级锁定空不需要记录信息11GC标记偏向线程ID、偏向时间戳、对象分代年龄01可偏向 对象头的另外一部分是类型指针即是对象指向它的类的元数据的指针虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针换句话说查找对象的元数据信息并不一定要经过对象本身。另外如果对象是一个Java数组那在对象头中还必须有一块用于记录数组长度的数据因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小但是从数组的元数据中无法确定数组的大小。 以下是HotSpot虚拟机markOop.cpp中的代码注释片段它描述了32bits下MarkWord的存储状态
1// Bit-format of an object header (most significant first, big endian layout below):
2//
3// 32 bits:
4// --------
5// hash:25 ------------| age:4 biased_lock:1 lock:2 (normal object)
6// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
7// size:32 ------------------------------------------| (CMS free block)
8// PromotedObject*:29 ----------| promo_bits:3 -----| (CMS promoted object) 接下来实例数据部分是对象真正存储的有效信息也既是我们在程序代码里面所定义的各种类型的字段内容无论是从父类继承下来的还是在子类中定义的都需要记录下来。 这部分的存储顺序会受到虚拟机分配策略参数FieldsAllocationStyle和字段在Java源码中定义顺序的影响。HotSpot虚拟机 默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oopsOrdinary Object Pointers从分配策略中可以看出相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下在父类中定义的变量会出现在子类之前。如果 CompactFields参数值为true默认为true那子类之中较窄的变量也可能会插入到父类变量的空隙之中。 第三部分对齐填充并不是必然存在的也没有特别的含义它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍换句话说就是对象的大小必须是8字节的整数倍。对象头正好是8字节的倍数1倍或者2倍因此当对象实例数据部分没有对齐的话就需要通过对齐填充来补全。 对象的访问定位 建立对象是为了使用对象我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于在Java虚拟机规范里面只规定了 reference类型 是一个指向对象的引用并没有定义这个引用应该通过什么种方式去定位、访问到堆中的对象的具体位置对象访问方式也是取决于虚拟机实现而定的。主流的访问方式有使用句柄和直接指针两种。 如果使用句柄访问的话Java堆中将会划分出一块内存来作为句柄池reference中存储的就是对象的句柄地址而句柄中包含了对象实例数据与类型数据的具体各自的地址信息。如图1所示。 图1 通过句柄访问对象 如果使用直接指针访问的话Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息reference中存储的直接就是对象地址如图2所示。 图2 通过直接指针访问对象 这两种对象访问方式各有优势使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址在对象被移动垃圾收集时移动对象是非常普遍的行为时只会改变句柄中的实例数据指针而reference本身不需要被修改。 使用直接指针来访问最大的好处就是速度更快它节省了一次指针定位的时间开销由于对象访问的在Java中非常频繁因此这类开销积小成多也是一项非常可 观的执行成本。从上一部分讲解的对象内存布局可以看出就虚拟机HotSpot而言它是使用第二种方式进行对象访问但在整个软件开发的范围来看各种 语言、框架中使用句柄来访问的情况也十分常见。