jvm内存占用模型
对象的内存结构
对象头 Header
包含两部分数据Mark Word和Kclass:
Mark Word:存储对象自身的运行时数据,如hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64的虚拟机(未开启指针压缩)中分别为4B和8B,官方称之为”Mark Word”。
类型指针 Kclass:即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
如果对象是一个Java数组,那再对象头中还必须有一块用于记录数组长度的数据。
对象头在32位系统上占用8B,64位系统上占16B。 无论是32位系统还是64位系统,对象都采用8字节对齐。Java在64位模式下开启指针压缩,比32位模式下,头部会大4B(mark区域变位8B,kclass区域被压缩为4B),如果没有开启指针压缩,头部会大8B(mark和kclass都是8B)
实例数据 Instance Data
存放字段数据。
对齐填充 Padding
对象的起始地址必须是8字节的整数倍(对象大小=8字节*整数),如果没有对齐时,需要通过对齐填充来补全。
综上,对象内存占用情况如下:
对象总内存 = 对象头(Header(Mark Word+Kclass))+实例数据(Instance Data)+对齐填充(Padding)
32位虚拟机:header (8B)=Mark Word(4B)+kclass(4B)
64位没有开启指针压缩:header (16B)=Mark Word(8B)+kclass(8B)
64位开启指针压缩:header (12B)=Mark Word(8B)+kclass(4B)
详细了解jvm理论
基于maven的内存分析工具
项目结构
SizeOfAgent
package com.mobjia.agent; import java.lang.instrument.Instrumentation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; /** * 对象占用字节大小工具类 * * @author tianmai.fh * @date 2014-03-18 11:29 */ public class SizeOfAgent { static Instrumentation inst; public static void premain(String args, Instrumentation instP) { inst = instP; } /** * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br> * 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br> * 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br> * * @param obj * @return */ public static long sizeOf(Object obj) { return inst.getObjectSize(obj); } /** * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 * * @param objP * @return * @throws IllegalAccessException */ public static long fullSizeOf(Object objP) throws IllegalAccessException { Set<Object> visited = new HashSet<Object>(); Deque<Object> toBeQueue = new ArrayDeque<Object>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); //sizeOf的时候已经计基本类型和引用的长度,包括数组 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本类型名字长度是2 if (tmpObjClass.getName().length() > 2) { for (int i = 0, len = Array.getLength(obj); i < len; i++) { Object tmp = Array.get(obj, i); if (tmp != null) { //非基本类型需要深度遍历其对象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //静态不计 || field.getType().isPrimitive()) { //基本类型不重复计 continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; } /** * String.intern的对象不计;计算过的不计,也避免死循环 * * @param visited * @param obj * @return */ static boolean skipObject(Set<Object> visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }
AgentMain
package com.mobjia.agent; import java.io.File; import java.util.HashMap; public class AgentMain { /** * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 = 16 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + padding/4 = 24 */ static class A { int a; } /** * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + 4 = 24 */ static class B { int a; int b; } /** * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 8 + 4 + padding/4 = 32 */ static class B2 { int b2a; Integer b2b; } /** * 不考虑对象头: * 4 + 4 + 4 * 3 + 3 * sizeOf(B) */ static class C extends A { int ba; B[] as = new B[3]; C() { for (int i = 0; i < as.length; i++) { as[i] = new B(); } } } static class D extends B { int da; Integer[] di = new Integer[3]; } /** * 会算上A的实例字段 */ static class E extends A { int ea; int eb; } public static void main(String[] args) throws IllegalAccessException { primitiveType(); wrapperType(); } private static void primitiveType(){ //逻辑型boolean boolean boolean1 =true; System.out.println("sizeOf(boolean)=" + SizeOfAgent.sizeOf(boolean1)); //文本型char char char1 = 0; System.out.println("sizeOf(char)=" + SizeOfAgent.sizeOf(char1)); //整数型(byte、short、int、long) byte byte1 = 0; System.out.println("sizeOf(byte)=" + SizeOfAgent.sizeOf(byte1)); short short1 = 0; System.out.println("sizeOf(short)=" + SizeOfAgent.sizeOf(short1)); int int1 = 0; System.out.println("sizeOf(int)=" + SizeOfAgent.sizeOf(int1)); long long1 = 0; System.out.println("sizeOf(long)=" + SizeOfAgent.sizeOf(long1)); //浮点型(float、double) float float1 = 0; System.out.println("sizeOf(float)=" + SizeOfAgent.sizeOf(float1)); double double1 =1; System.out.println("sizeOf(double)=" + SizeOfAgent.sizeOf(double1)); } private static void wrapperType(){ //逻辑型boolean java.lang.Boolean boolean1 =true; System.out.println("sizeOf(java.lang.boolean)=" + SizeOfAgent.sizeOf(boolean1)); //文本型char java.lang.Character char1 = 0; System.out.println("sizeOf(java.lang.Character)=" + SizeOfAgent.sizeOf(char1)); //整数型(byte、short、int、long) java.lang.Byte byte1 = 0; System.out.println("sizeOf(java.lang.Byte)=" + SizeOfAgent.sizeOf(byte1)); java.lang.Short short1 = 0; System.out.println("sizeOf(java.lang.Short)=" + SizeOfAgent.sizeOf(short1)); java.lang.Short int1 = 0; System.out.println("sizeOf(java.lang.Short)=" + SizeOfAgent.sizeOf(int1)); java.lang.Long long1 = 0l; System.out.println("sizeOf(java.lang.Long)=" + SizeOfAgent.sizeOf(long1)); //浮点型(float、double) java.lang.Float float1 = 0f; System.out.println("sizeOf(java.lang.Float)=" + SizeOfAgent.sizeOf(float1)); java.lang.Double double1 =1d; System.out.println("sizeOf(java.lang.Double)=" + SizeOfAgent.sizeOf(double1)); } }
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mobjia</groupId> <artifactId>mobjia-jvm</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>mobjia-jvm</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <finalName>SizeOfAgent</finalName> <archive> <manifestEntries> <Premain-class>com.mobjia.agent.SizeOfAgent</Premain-class> <Boot-Class-Path></Boot-Class-Path> <Can-Redefine-Classes>false</Can-Redefine-Classes> </manifestEntries> <addMavenDescriptor>false</addMavenDescriptor> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.mobjia.agent.AgentMain</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> <defaultGoal>compile</defaultGoal> </build> </project>
java基本类型内存占用分析
以下是基于64位HotSpot虚拟机。
生成jar包
通过maven install 直接生成jar包,jar包在 项目\target文件夹下。
基本类型内存占用情况
开启指针压缩
通过vm参数 -XX:+UseCompressedOops 开启指针压缩
boolean
对象头
header(12B)=Mark Word(8B)+kclass(4b)
实例数据
Instance Data (1B)
对齐填充
Padding = 2*8B -( header(12B)+(1B)) = 3B
所以boolean占用16B
int
对象头
header(12B)=Mark Word(8B)+kclass(4b)
实例数据
Instance Data (4B)
对齐填充
Padding = 2*8B -( header(12B)+(4B)) = 0B
所以int占用16B
long
对象头
header(12B)=Mark Word(8B)+kclass(4b)
实例数据
Instance Data (8B)
对齐填充
Padding = 3*8B -( header(12B)+(8B)) = 4B
所以long占用24B
关闭指针压缩
通过vm参数 -XX:-UseCompressedOops 关闭指针压缩
boolean
对象头
header(16B)=Mark Word(8B)+kclass(8b)
实例数据
Instance Data (1B)
对齐填充
Padding = 3*8B -( header(16B)+(1B)) = 7B
所以boolean占用24B
int
对象头
header(16B)=Mark Word(8B)+kclass(8b)
实例数据
Instance Data (4B)
对齐填充
Padding = 3*8B -( header(16B)+(4B)) = 4B
所以int占用24B
long
对象头
header(16B)=Mark Word(8B)+kclass(8b)
实例数据
Instance Data (8B)
对齐填充
Padding = 3*8B -( header(16B)+(8B)) = 0B
所以long占用24B