当前位置: 首页 > news >正文

服务器做php网站吗深圳做微信商城网站建设

服务器做php网站吗,深圳做微信商城网站建设,重庆市住房城乡建设网站,东莞十大保安公司摘要#xff1a; 本文以如何计算Java对象占用内存大小为切入点#xff0c;在讨论计算Java对象占用堆内存大小的方法的基础上#xff0c;详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍#xff0c;涉及内存模型、锁原理、分代GC、OOP-Klass模型等内容。…摘要 本文以如何计算Java对象占用内存大小为切入点在讨论计算Java对象占用堆内存大小的方法的基础上详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍涉及内存模型、锁原理、分代GC、OOP-Klass模型等内容。关键词HotspotVM、Java对象头、HSDB、锁原理、分代GC、OOP-Klass摘要       本文以如何计算Java对象占用内存大小为切入点在讨论计算Java对象占用堆内存大小的方法的基础上详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍涉及内存模型、锁原理、分代GC、OOP-Klass模型等内容。最后推荐JDK自带的Hotspot Debug工具——HSDB来查看对象在内存中的具体存在形式以论证文中所述内容。背景        目前我们系统的业务代码中大量使用了LocalCache的方式做本地缓存而且cache的maxSize通常设的比较大比如10000。我们的业务系统中就使用了size为10000的15个本地缓存所以最坏情况下将可缓存15万个对象。这会消耗掉不菲的本地堆内存而至于实际上到底应该设多大容量的缓存、运行时这大量的本地缓存会给堆内存带来多少压力实际占用多少内存大小会不会有较高的缓存穿透风险目前并不方便知悉。考虑到对缓存实际占用内存的大小能有个更直观和量化的参考需要对运行时指定对象的内存占用进行评估和计算。       要计算Java对象占用内存的大小首先需要了解Java对象在内存中的实际存储方式和存储格式。       另一方面大家都了解Java对象的存储总得来说会占用JVM内存的堆内存、栈内存及方法区但由于栈内存中存放的数据可以看做是运行时的临时数据主要表现为本地变量、操作数、对象引用地址等。这些数据会在方法执行结束后立即回收掉不会驻留。对存储空间空间的占用也只是执行函数指令时所必须的空间。通常不会造成内存的瓶颈。而方法区中存储的则是对象所对应的类信息、函数表、构造函数、静态常量等这些信息在类加载时(按需)只会在方法区中存储一份不会产生额外的存储空间。因此本文所要讨论的主要目标是Java对象对堆内存的占用。内存占用计算方法        如果读者关心对象在JVM中的存储原理可阅读本文后边几个小节中关于对象存储原理的介绍。如果不关心对象存储原理而只想直接计算内存占用的话其实并不难笔者这里总结了三种方法以供参考1. Instrumentation      使用java.lang.instrument.Instrumentation.getObjectSize()方法可以很方便的计算任何一个运行时对象的大小返回该对象本身及其间接引用的对象在内存中的大小。不过这个类的唯一实现类InstrumentationImpl的构造方法是私有的在创建时需要依赖一个nativeAgent和运行环境所支持的一些预定义类信息我们在代码中无法直接实例化它需要在JVM启动时通过指定代理的方式让JVM来实例化它。      具体来讲就是需要声明一个premain方法它和main方法的方法签名有点相似只不过方法名叫“premain”同时方法参数也不一样它接收一个String类型和instrumentation参数而String参数实际上和String[]是一样的只不过用String统一来表达的。在premain函数中将instrumentation参数赋给一个静态变量其它地方就可以使用了。如/*** author yepei* date 2018/04/23* description*/ public class SizeTool {private static Instrumentation instrumentation;public static void premain(String args, Instrumentation inst) {instrumentation inst;}public static long getObjectSize(Object o) {return instrumentation.getObjectSize(o);} }      从方法名可以猜到这里的premain是要先于main执行的而先于main执行这个动作只能由JVM来完成了。即在JVM启动时先启动一个agent操作如下      假设main方法所在的jar包为A.jarpremain方法所在的jar包为B.jar。注意为main所在的代码打包时和其它工具类打包一样需要声明一个MANIFEST.MF清单文件如下所求Manifest-Version: 1.0 Main-Class: yp.tools.Main Premain-Class: yp.tools.SizeTool       然后执行java命令执行jar文件java -javaagent:B.jar -jar A.jar      点评这种方法的优点是编码简单缺点就是必须启动一个javaagent因此要求修改Java的启动参数。2. 使用Unsafe       java中的sun.misc.Unsafe类有一个objectFieldOffset(Field f)方法表示获取指定字段在所在实例中的起始地址偏移量如此可以计算出指定的对象中每个字段的偏移量值为最大的那个就是最后一个字段的首地址加上该字段的实际大小就能知道该对象整体的大小。如现有一Person类class Person{int age;String name;boolean married; }       假设该类的一个实例p通过Unsafe.objectFieldOffset()方法计算到得age/birthday/married三个字段的偏移量分别是1621,  17则表明p1对象中的最后一个字段是name它的首地址是21由于它是一个引用所以它的大小默认为4(开启指针压缩)则该对象本身的大小就是214 7 32字节。其中7表示padding即为了使结果变成8的整数倍而做的padding。       但上述计算只是计算了对象本身的大小并没有计算其所引用的引用类型的最终大小这就需要手工写代码进行递归计算了。       点评使用Unsafe可以完全不care对象内的复杂构成可以很精确的计算出对象头的大小(即第一个字段的偏移)及每个字段的偏移。缺点是Unsafe通常禁止开发者直接使用需要通过反射获取其实例另外最后一个字段的大小需要手工计算。其次需要手工写代码递归计算才能得到对象及其所引用的对象的综合大小相对比较麻烦。3. 使用第三方工具        这里要介绍的是lucene提供的专门用于计算堆内存占用大小的工具类RamUsageEstimatormaven坐标dependencygroupIdorg.apache.lucene/groupIdartifactIdlucene-core/artifactIdversion4.0.0/version /dependency       RamUsageEstimator就是根据java对象在堆内存中的存储格式通过计算Java对象头、实例数据、引用等的大小相加而得如果有引用还能递归计算引用对象的大小。RamUsageEstimator的源码并不多几百行清晰可读。这里不进行一一解读了。它在初始化的时候会根据当前JVM运行环境、CPU架构、运行参数、是否开启指针压缩、JDK版本等综合计算对象头的大小而实例数据部分则按照java基础数据类型的标准大小进行计算。思路简单同时也在一定程度上反映出了Java对象格式的奥秘       常用方法如下//计算指定对象及其引用树上的所有对象的综合大小单位字节 long RamUsageEstimator.sizeOf(Object obj)//计算指定对象本身在堆空间的大小单位字节 long RamUsageEstimator.shallowSizeOf(Object obj)//计算指定对象及其引用树上的所有对象的综合大小返回可读的结果如2KB String RamUsageEstimator.humanSizeOf(Object obj)      点评使用该第三方工具比较简单直接主要依靠JVM本身环境、参数及CPU架构计算头信息再依据数据类型的标准计算实例字段大小计算速度很快另外使用较方便。如果非要说这种方式有什么缺点的话那就是这种方式计算所得的对象头大小是基于JVM声明规范的并不是通过运行时内存地址计算而得存在与实际大小不符的这种可能性。Java对象格式     在HotSpot虚拟机中Java对象的存储格式也是一个协议或者数据结构底层是用C代码定义的。Java对象结构大致如下图所示——即Java对象从整体上可以分为三个部分对象头、实例数据和对齐填充       对象头Instance HeaderJava对象最复杂的一部分采用C定义了头的协议格式存储了Java对象hash、GC年龄、锁标记、class指针、数组长度等信息稍后做出详细解说。       实例数据Instance Data这部分数据才是真正具有业务意义的数据实际上就是当前对象中的实例字段。在VM中对象的字段是由基本数据类型和引用类型组成的。其所占用空间的大小如下所示  类型大小(字节)类型大小(字节)  byte1int4  boolean1float4  char2long8  short2double8  ref4(32bit)   OR   8(64bit)  OR  4(64bit -XX:UseCompressedOops)          说明其中ref表示引用类型引用类型实际上是一个地址指针32bit机器上占用4字节64bit机器上在jdk1.6之后如果开启了指针压缩(默认开启: -XX:UseCompressedOops仅支持64位机器)则占用4字节。Java对象的所有字段类型都可映射为上述类型之一因此实例数据部分的大小实际上就是这些字段类型的大小之和。当然实际情况可能比这个稍微复杂一点如字段排序、内部padding以及父类字段大小的计算等。         对齐填充PaddingVM要求对象大小须是8的整体数该部分是为了让整体对象在内存中的地址空间大小达到8的整数倍而额外占用的字节数。对象头       对象头是理解JVM中对象存储方式的最核心的部分甚至是理解java多线程、分代GC、锁等理论的基础也是窥探JVM底层诸多实现细节的出发点。做为一个java程序猿这是不可不了解的一部分。那么这里提到的对象头到底是什么呢参考OpenJDK中JVM源码部分对对象头的C定义如下class oopDesc {friend class VMStructs;private:volatile markOop _mark;union _metadata {wideKlassOop _klass;narrowOop _compressed_klass;} _metadata;... }       源码里的 _mark 和 _metadata两个字段就是对象头的定义分别表示对象头中的两个基本组成部分_mark用于存储hash、gc年龄、锁标记、偏向锁、自旋时间等而_metadata是个共用体(union)即_klass字段或_compressed_klass存储当前对象到所在class的引用而这个引用的要么由“_klass”来存储要么由“_compressed_klass”来存储其中_compressed_klass表示压缩的class指针即当JVM开启了 -XX:UseCompressedOops选项时就表示启用指针压缩选项自然就使用_commpressed_klass来存储class引用了否则使用_klass。       注意到_mark的类型是 markOop而_metadata的类型是union_metadata内部两个字段_klass和_compressed_klass类型分别为wideKlassOop和narrowOop分别表示什么意思呢这里顺便说一个union联合体的概念这是在C中的一种结构声明类似struct称作“联合”它是一种特殊的类也是一种构造类型的数据结构。在一个“联合”内可以定义多种不同的数据类型 一个被说明为该“联合”类型的变量中允许装入该“联合”所定义的任何一种数据这些数据共享同一段内存已达到节省空间的目的。由此可见刚刚所说的使用-XX:UseCompressedOops后就自动使用_metadata中的_compressed_klass来作为指向当前对象的class引用它的类型是narrowOop。可以看到对象头中的两个字段的定义都包含了“Oop”字眼不难猜出这是一种在JVM层定义好的“类型”。OOP-Klass模型       实际上Java的面向对象在语言层是通过java的class定义实现的而在JVM层也有对应的实现那就是Oop模型。所谓Oop模型全称Ordinary Object Pointer即普通对象指针。JVM层用于定义Java对象模型及一些元数据格式的模型就是Oop可以认为是JVM层中的“类”。通过JDK源码可以看到有很多模型定义的名称都是以Oop结尾arrayOop/markOop/instanceOop/methodOop/objectArrayOop等什么意思呢       HotSpot是基于c语言实现的它最核心的地方是设计了两种模型,分别是OOP和Klass称之为OOP-Klass Model.  其中OOP用来将指针对象化比C底层使用的*更好用每一个类型的OOP都代表一个在JVM内部使用的特定对象的类型。而Klass则用来描述JVM层面中对象实例的具体类型它是java实现语言层面类型的基础或者说是对java语言层类型的VM层描述。所以看到openJDK源码中的定义基本都以Oop或Klass结尾如图所示       由上述定义可以简单的说Oop就是JVM内部对象类型而Klass就是java类在JVM中的映射。其中关于Oop和Klass体系参考定义https://github.com/openjdk-mirror/jdk7u-hotspot/blob/50bdefc3afe944ca74c3093e7448d6b889cd20d1/src/share/vm/oops/oop.hppJVM中把我们上层可见的Java对象在底层实际上表示为两部分分别是oop和klass其中oop专注于表示对象的实例数据不关心对象中的实例方法(包括继承、重载等)所对应的函数表。而klass则维护对象到java class及函数表的功能它是java class及实现多态的基础。这里列举几个基础的Oop和Klass——Oop://定义了oops共同基类 typedef class oopDesc* oop; //表示一个Java类型实例 typedef class instanceOopDesc* instanceOop; //表示一个Java方法 typedef class methodOopDesc* methodOop; //定义了数组OOPS的抽象基类 typedef class arrayOopDesc* arrayOop; //表示持有一个OOPS数组 typedef class objArrayOopDesc* objArrayOop; //表示容纳基本类型的数组 typedef class typeArrayOopDesc* typeArrayOop; //表示在Class文件中描述的常量池 typedef class constantPoolOopDesc* constantPoolOop; //常量池告诉缓存 typedef class constantPoolCacheOopDesc* constantPoolCacheOop; //描述一个与Java类对等的C类 typedef class klassOopDesc* klassOop; //表示对象头 typedef class markOopDesc* markOop;Klass://klassOop的一部分用来描述语言层的类型 class Klass; //在虚拟机层面描述一个Java类 class instanceKlass; //专有instantKlass表示java.lang.Class的Klass class instanceMirrorKlass; //表示methodOop的Klass class methodKlass; //最为klass链的端点klassKlass的Klass就是它自身 class klassKlass; //表示array类型的抽象基类 class arrayKlass; //表示constantPoolOop的Klass class constantPoolKlass;结合上述JVM层与java语言层java对象的表示关系如下所示      其中OopDesc是对象实例的基类(Java实例在VM中表现为instanceOopDesc)Klass是类信息的基类(Java类在VM中表现为instanceKlass)klassKlass则是对Klass本身的描述(Java类的class对象在VM中表现为klassKlass)。       有了对上述结构的认识对应到内存中的存储区域那么对象是怎么存储的就了比较清楚的认识对象实例(instanceOopDesc)保存在堆上对象的元数据(instanceKlass)保存在方法区对象的引用则保存在栈上。       因此关于本小节对OOP-Klass Model的讨论可以用一句简洁明了的话来总结其意义一个Java类在被VM加载时JVM会为其在方法区创建一个instanceKlass来表示该类的class信息。当我们在代码中基于此类用new创建一个新对象时实际上JVM会去堆上创建一个instanceOopDesc对象该对象保含对象头markWord和klass指针klass指针指向方法区中的instanceKlass,markWord则保存一些锁、GC等相关的运行时数据。而在堆上创建的这个instanceOopDesc所对应的地址会被用来创建一个引用赋给当前线程运行时栈上的一个变量。关于Mark Word       mark word是对象头中较为神秘的一部分也是本文讲述的重点JDK oop.hpp源码文件中有几行重要的注释揭示了32位机器和64位机器下对象头的格式// Bit-format of an object header (most significant first, big endian layout below): // // 32 bits: // -------- // hash:25 ------------| age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) // size:32 ------------------------------------------| (CMS free block) // PromotedObject*:29 ----------| promo_bits:3 -----| (CMS promoted object) // // 64 bits: // -------- // unused:25 hash:31 --| unused:1 age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object) // PromotedObject*:61 ---------------------| promo_bits:3 -----| (CMS promoted object) // size:64 -----------------------------------------------------| (CMS free block) // // unused:25 hash:31 --| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs normal object) // JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs biased object) // narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 -----| (COOPs CMS promoted object) // unused:21 size:35 --| cms_free:1 unused:7 ------------------| (COOPs CMS free block)在oop.hpp源码文件中有对Oop基类中mark word结构的定义如下class oopDesc {friend class VMStructs;private:volatile markOop _mark;union _metadata {wideKlassOop _klass;narrowOop _compressed_klass;} _metadata;... }       其中的mark word即上述 _mark字段它在JVM中的表示类型是markOop, 部分关键源码如下所示源码中展示了markWord各个字段的意义及占用大小(与机器字宽有关系)如GC分代年龄、锁状态标记、哈希码、epoch、是否可偏向等信息... class markOopDesc: public oopDesc {private:// Conversionuintptr_t value() const { return (uintptr_t) this; }public:// Constantsenum { age_bits 4,lock_bits 2,biased_lock_bits 1,max_hash_bits BitsPerWord - age_bits - lock_bits - biased_lock_bits,hash_bits max_hash_bits 31 ? 31 : max_hash_bits,cms_bits LP64_ONLY(1) NOT_LP64(0),epoch_bits 2};// The biased locking code currently requires that the age bits be// contiguous to the lock bits.enum { lock_shift 0,biased_lock_shift lock_bits,age_shift lock_bits biased_lock_bits,cms_shift age_shift age_bits,hash_shift cms_shift cms_bits,epoch_shift hash_shift}; ...         因为对象头信息只是对象运行时自身的一部分数据相比实例数据部分头部分属于与业务无关的额外存储成功。为了提高对象对堆空间的复用效率Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息它会根据对象的状态复用自己的存储空间。        对于上述源码mark word中字段枚举意义解释如下hash 保存对象的哈希码age 保存对象的分代年龄biased_lock 偏向锁标识位lock 锁状态标识位JavaThread* 保存持有偏向锁的线程IDepoch 保存偏向时间戳       锁标记枚举的意义解释如下locked_value 0,//00 轻量级锁 unlocked_value 1,//01 无锁 monitor_value 2,//10 监视器锁也叫膨胀锁也叫重量级锁 marked_value 3,//11 GC标记 biased_lock_pattern 5 //101 偏向锁       实际上markword的设计非常像网络协议报文头将mark word划分为多个比特位区间并在不同的对象状态下赋予不同的含义, 下图是来自网络上的一张协议图。 上述协议字段正对应着源码中所列的枚举字段这里简要进行说明一下。hash        对象的hash码hash代表的并不一定是对象的虚拟内存地址但依赖于内存地址具体取决于运行时库和JVM的具体实现底层由C实现实现细节参考OpenJDK源码。但可以简单的理解为对象的内存地址的整型值。age        对象分代GC的年龄。分代GC的年龄是指Java对象在分代垃圾回收模型下(现在JVM实现基本都使用的这种模型)对象上标记的分代年龄当该年轻代内存区域空间满后或者到达GC最达年龄时会被扔进老年代等待老年代区域满后被FullGC收集掉这里的最大年龄是通过JVM参数设定的-XX:MaxTenuringThreshold 默认值是15。那这个年龄具体是怎么计算的呢       下图展示了该年龄递增的过程1. 首先在对象被new出来后放在Eden区年龄都是02. 经过一轮GC后B0和F0被回收其它对象被拷贝到S1区年龄增加1注如果S1不能同时容纳A0,C0,D0,E0和G0将被直接丢入Old区3. 再经一轮GCEden区中新生的对象M0,P0及S1中的B1,E1,G1不被引用将被回收而H0,K0,N0及S1中的A1,D1被拷贝到S2区中对应年龄增加14. 如此经过2、3过滤循环进行当S1或S2满或者对象的年龄达到最大年龄(15)后仍然有引用存在则对象将被转移至Old区。锁标记lock/biased_lock/epoch/JavaThread*        锁标记位此锁为重量级锁即对象监视器锁。Java在使用synchronized关键字对方法或块进行加锁时会触发一个名为“objectMonitor”的监视器对目标代码块执行加锁的操作。当然synchronized方法和synchronized代码块的底层处理机制稍有不同。synchronized方法编译后会被打上“ACC_SYNCHRONIZED”标记符。而synchronized代码块编译之后会在同步代码的前后分别加上“monitorenter”和“monitorexit”的指令。当程序执行时遇到到monitorenter或ACC_SYNCHRONIZED时会检测对象头上的lock标记位该标记位被如果被线程初次成功访问并设值则置为1表示取锁成功如果再次取锁再执行操作。在代码块执行结束等待返回或遇到异常等待抛出时会执行monitorexit或相应的放锁操作锁标记位执行--操作如果减到0则锁被完全释放掉。关于objectMonitor的实现细节参考JDK源码        注意在jdk1.6之前synchronized加锁或取锁等待操作最终会被转换为操作系统中线程操作原语如激活、阻塞等。这些操作会导致CPU线程上下文的切换开销较大因此称之为重量级锁。但后续JDK版本中对其实现做了大幅优化相继出现了轻量级锁偏向锁自旋锁自适应自旋锁锁粗化及锁消除等策略。这里仅做简单介绍不进行展开。        如图所示展示了这几种锁的关系              轻量级锁如上图所示是当某个资源在没有竞争或极少竞争的情况下JVM会优先使用CAS操作让线程在用户态去尝试修改对象头上的锁标记位从而避免进入内核态。这里CAS尝试修改锁标记是指尝试对指向当前栈中保存的lock record的线程指针的修改即对biased_lock标记做CAS修改操作。如果发现存在多个线程竞争(表现为CAS多次失败)则膨胀为重量级锁修改对应的lock标记位并进入内核态执行锁操作。注意这种膨胀并非属于性能的恶化相反如果竞争较多时CAS方式的弊端就很明显因为它会占用较长的CPU时间做无谓的操作。此时重量级锁的优势更明显。       偏向锁是针对只会有一个线程执行同步代码块时的优化如果一个同步块只会被一个线程访问则偏向锁标记会记录该线程id当该线程进入时只用check 线程id是否一致而无须进行同步。锁偏向后会依据epoch(偏向时间戳)及设定的最大epoch判断是否撤销锁偏向。       自旋锁大意是指线程不进入阻塞等待而只是做自旋等待前一个线程释放锁。不在对象头讨论范围之列这里不做讨论。实例数据       实例数据instance Data是占用堆内存的主要部分它们都是对象的实例字段。那么计算这些字段的大小主要思路就是根据这些字段的类型大小进行求和的。字段类型的标准大小如Java对象格式概述中表格描述的除了引用类型会受CPU架构及是否开启指针压缩影响外其它都是固定的。因此计算起来比较简单。但实际情其实并不这么简单例如如下对象class People{int age 20;String name Xiaoming; } class Person extends People{boolean married false;long birthday 128902093242L;char tag c;double sallary 1200.00d; }       Person对象实例数据的大小应该是多少呢这里假设使用64位机器采用指针压缩则对象头的大小为8(_mark)4(_klass) 12       然后实例数据的大小为 4(age)4(name) 8(birthday) 8(sallary) 2(tag) 1(married) 27       因此最终的对象本身大小为12271(padding) 40字节       注意为了尽量减少内存空间的占用这里在计算的过程中需要遵循以下几个规则/*** 1: 除了对象整体需要按8字节对齐外每个成员变量都尽量使本身的大小在内存中尽量对齐。比如 int 按 4 位对齐long 按 8 位对齐。* 2类属性按照如下优先级进行排列长整型和双精度类型整型和浮点型字符和短整型字节类型和布尔类型最后是引用类型。这些属性都按照各自的单位对齐。* 3优先按照规则一和二处理父类中的成员接着才是子类的成员。* 4当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话就必须扩展到4个字节的基本单位。* 5如果子类第一个成员是一个双精度或者长整型并且父类并没有用完8个字节JVM会破坏规则2按照整形int短整型short字节型byte引用类型reference的顺序向未填满的空间填充。*/       最后计算引用类型字段的实际大小Xiaoming按字符串对象的字段进行计算对象头12字节hash字段4字节char[] 4字节共12444(padding) 24字节其中char[]又是引用类型且是数组类型其大小为对象头124(length) 9(arrLength) * 2(char) 4(padding) 40字节。      所以综上所述一个Person对象占用内存的大小为104字节。关于指针压缩      一个比较明显的问题是在64位机器上如果开启了指针压缩后则引用只占用4个字节4字节的最大寻址空间为2^324GB, 那么如何保证能满足寻址空间大于4G的需求呢      开启指针压缩后实际上会压缩的对象包括每个Class的属性指针(静态成员变量)及每个引用类型的字段(包括数组)指针而本地变量堆栈元素入参返回值NULL这些指针不会被压缩。在开启指针压缩后如前文源码所述markWord中的存储指针将是_compressed_klass对应的类型是narrowOop不再是wideKlassOop了有什么区别呢      wideKlassOop和narrowOop都指向InstanceKlass对象其中narrowOop指向的是经过压缩的对象。简单来说wideKlassOop可以达到整个寻址空间。而narrowOop虽然达不到整个寻址空间但它面对也不再是个单纯的byte地址而是一个object也就是说使用narrowOop后压缩后的这4个字节表示的4GB实际上是4G个对象的指针大概是32GB。JVM会对对应的指针对象进行解码, JDK源码中oop.hpp源码文件中定义了抽象的编解码方法用于将narrowOop解码为一个正常的引用指针或将一下正常的引用指针编码为narrowOop // Decode an oop pointer from a narrowOop if compressed.// These are overloaded for oop and narrowOop as are the other functions// below so that they can be called in template functions.static oop decode_heap_oop_not_null(oop v);static oop decode_heap_oop_not_null(narrowOop v);static oop decode_heap_oop(oop v);static oop decode_heap_oop(narrowOop v);// Encode an oop pointer to a narrow oop. The or_null versions accept// null oop pointer, others do not in order to eliminate the// null checking branches.static narrowOop encode_heap_oop_not_null(oop v);static narrowOop encode_heap_oop(oop v);对齐填充         对齐填充是底层CPU数据总线读取内存数据时的要求例如通常CPU按照字单位读取如果一个完整的数据体不需要对齐那么在内存中存储时其地址有极大可能横跨两个字例如某数据块地址未对齐存储为1-4而cpu按字读取需要把0-3字块读取出来再把4-7字块读出来最后合并舍弃掉多余的部分。这种操作会很多很多且很频繁但如果进行了对齐则一次性即可取出目标数据将会大大节省CPU资源。        在hotSpot虚拟机中默认的对齐位数是8与CPU架构无关如下代码中的objectAlignment// Try to get the object alignment (the default seems to be 8 on Hotspot, // regardless of the architecture).int objectAlignment 8;try {final Class? beanClazz Class.forName(com.sun.management.HotSpotDiagnosticMXBean);final Object hotSpotBean ManagementFactory.newPlatformMXBeanProxy(ManagementFactory.getPlatformMBeanServer(),com.sun.management:typeHotSpotDiagnostic,beanClazz);final Method getVMOptionMethod beanClazz.getMethod(getVMOption, String.class);final Object vmOption getVMOptionMethod.invoke(hotSpotBean, ObjectAlignmentInBytes);objectAlignment Integer.parseInt(vmOption.getClass().getMethod(getValue).invoke(vmOption).toString());supportedFeatures.add(JvmFeature.OBJECT_ALIGNMENT);} catch (Exception e) {// Ignore.}NUM_BYTES_OBJECT_ALIGNMENT objectAlignment;         可以看出通过HotSpotDiagnosticMXBean.getVMOption(ObjectAlignmentBytes).getValue()方法可以拿到当前JVM环境下的对齐位数。        注意这里的HotSpotDiagnosticMXBean是JVM提供的JMX中一种可被管理的资源即HotSpot信息资源。使用SA Hotspot Debuger(HSDB)查看oops结构        前文所述都是源码理论其实Hotspot为我们提供了一种工具可以方便的用来查询运行时对象的Oops结构即SA Hotspot Debuger简称HSDB. 其中SA指“Serviceability Agent”它是一个JVM服务工具集的Agent它原本是sun公司用来debug Hotspot的工具现在开放给开发者使用能够查看Java对象的oops、查看类信息、线程栈信息、堆信息、方法字节码和JIT编译后的汇编代码等。SA提供的入口在$JAVA_HOME/lib/sa-jdi.jar中包含了很多工具其中最常用的工具就是HSDB。       下面演示一下HSDB的使用——        1. 先准备如下代码并运行public class Obj{private int age;private long height;private boolean married;private String name;private String addr;private String sex;...get/set }package yp.tools;/*** author yepei* date 2018/05/14* description*/ public class HSDBTest {public static void main(String[] args) throws InterruptedException {Obj o new Obj(20, 175, false, 小明, 浙江杭洲, 男);Thread.sleep(1000 * 3600);System.out.println(o);} }2. 执行jps命令获取当前运行的Java进程号               3. 启动HSDB并添加目标进程:        sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB         可以看到当前Java进程中的线程信息         双击指定线程可以查看到当前线程对象的Oop结构信息可以看到线程对象头也是包含_mark和_metadata两个协议字段的         点击上方的栈图标可以查询当前线程的栈内存         那么如何查看当前线程中用户定义的类结存储信息呢         先到方法区去看一下类信息吧         Tools——Class Browser搜索目标类          可以看到该类对应的对象的各个字段的偏移量最大的是36String类型意味着该对象本身的大小就是364 40字节。同时下方可以看到这个类相关的函数表、常量池信息。         要查看对象信息从Tools菜单打开Object Histogram         在打开的窗口中搜索目标类yp.tools.Obj         双击打开         点击Inspect查看该对象的Oop结构信息         如上图所示即是对象Obj的Oop结构对象头包含_mark与代表class指针的_metadata。示例中的类没有并发或锁的存在所以mark值是001代表无锁状态。         除此之外HSDB还有其它一些不错的功能如查看反编译信息、根据地址查找对象、crash分析、死锁分析等。总结          本文围绕“计算Java对象占用内存大小”这一话题简要介绍了直接计算指定对象在内存中大小的三种方法使用Instrumentation、Unsafe或第三方工具(RamUsageEstimator)的方式其中Instrumentation和Unsafe计算精确但使用起来不太方便Instrumentation需要以javaagent代理的方式启动而Unsafe只能计算指定对象的每个字段的地址起始位置偏移量需要手工递归并增加padding才能完整计算对象大小使用RamUsageEstimator可以很方便的计算对象本身或对象引用树整体的大小但其并非直接基于对象的真实内存地址而计算的而是通过已知JVM规则和数据类型的标准大小推算的存在计算误差的可能性。            为了揭开Java对象在堆内存中存储格式的面纱结合OpenJDK源码本文着重讨论了Java对象的格式对象头、实例数据及对齐填充三部分。其中对象头最为复杂包含_mark、_klass以及_length(仅数组类型)的协议字段。其中的mark word字段较为复杂甚至涉及了OOP-Klass模型、hash、gc、锁的原理及指针压缩等知识。        最后从实践的方面入手介绍了JDK自带的Hotspot Debuger工具——HSDB的使用透过它能够让我们更直观的查看运行中的java对象在内存中的存在形式和状态如对象的oops、类信息、线程栈信息、堆信息、方法字节码和JIT编译后的汇编代码等。        本文查询了一些资料并参考了OpenJDK源码。可能会有些不正确的地方敬请指正欢迎探讨。参考资料OpenJDK源码Hotspot虚拟机对象探秘《深入理解Java虚拟机》第2版周志明机械工业出版社Java ServiceabilityAgent(HSDB)使用和分析原文链接本文为云栖社区原创内容未经允许不得转载。
http://www.huolong8.cn/news/175230/

相关文章:

  • 店招搜索栏在那个网站上可以做wordpress怎么代码高亮
  • 厦门哪家网站建设最好提供网站建设备案
  • 网站如何开通支付功能怎么做网站的站点地图
  • 做网站运营需要培训吗怎样找竞争对手网站
  • 兰州网站建设平台分析网站内容转载
  • 搜狗做网站怎么样北京企业网站建设方案
  • a032网站模版深圳网站建设信科网络
  • 官网网站设计政协机关网站建设
  • 阜阳集团网站建设免费建网站的app
  • 怎么做微课网站网游开发
  • 东莞网站建设优化方案wordpress相册代码
  • 怎么在wordpress免费注册博客网站wordpress炫酷主题
  • 排名好的徐州网站建设网页设计项目案例网站
  • 青岛做网站推广公司网站建设全流程
  • 网站备案期间怎么做凡科建站电脑版网址
  • 做购物网站的数据库做网站用什么软件
  • 建站个人网站彩票网站建设与推广
  • 百家号淄博圻谷网站建设网易企业邮箱属于什么类型
  • 天津网站推广¥做下拉去118cr社区电商小程序模板包含哪些
  • 山西做二级建筑资料在哪个网站吴中seo外链推广工具
  • 知名网站定制公司电话国外英文网站
  • 如何做设计网站页面设计南京高新区建设规划局网站
  • FLASK做wiki网站网站开发需要哪些条件
  • 哈尔滨网站建设价位湖南网站建设公司 都来磐石网络
  • 电商专业培训网站建设自贡网站推广
  • 银川网站建设nx110网站换空间商什么意思
  • 中山网站建设文化效果做网站制作一般多少钱
  • 工程建设资料员报名网站如何做影视网站的标题
  • 企业网站设计的方案网站开发毕设论文
  • 网站开发后端做什么白城网站建设公司