`
sunqi
  • 浏览: 227767 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java对象占用内存大小

    博客分类:
  • java
 
阅读更多

new Object()将占用多少bytes的内存空间?

 原生类型(primitive type)的内存占用
Primitive Type             Memory Required(bytes)
—————————————————————
boolean                      1
byte                            1
short                           2
char                            2
int                               4
float                            4
long                            8
double                        8

 

对象在内存中存储的布局可以分为三块区域:对象头(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用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志 位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示。  

表1 HotSpot虚拟机对象头Mark Word

存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 膨胀(重量级锁定)
空,不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01

可偏向

 

 

1. 一个object header, 也称object overhead, 保存当前实例的type信息和内置monitor信息等, 32位系统上占用8bytes,64位系统上占用16bytes;
2. 0到多个fields, reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes; primitive类型参考上面;
3. padding, 对步骤1、2之和的补长。CPU从内存中读取数据是以word为基本单位, 32位的系统中word宽度为32bits, 64位的系统中word宽度为64bits, 将整个Java对象占用内存补长为word的整倍数大大提高了CPU存取数据的性能,参考维基百科关于数据alignment的说明。 就Hotspot而言,不管是32位系统还是64位系统要求(步骤1 + 步骤2 + padding) % 8等于0且0 <= padding < 8。例如在64位系统上:

public class Student {
    private int age;
}

如new Student()则其占用内存: 16 + 4 = 20,按照3中的说明则padding为4bytes,这样整个内存占用为24bytes。

六. 一维原生数组的内存占用
——————————————————————————————–
1. 在32位的系统中, 占用内存为: 型别占用内存 * 数组长度 + 8(数组在JVM中被当成特殊的对象, object overhead占用8bytes) + 4(数组长度) + padding。如:
byte[2], 型别占用内存,即byte型别占用1byte,数组长度为2,这样占用的总内存为1 * 2 + 8 + 4 = 14,padding上2bytes为16bytes,所以byte[2]占用内存为16bytes。
2. 在64位的系统中, 占用内存为: 型别占用内存 * 数组长度 + 16(object overhead占用16bytes) + 8(数组长度) + padding。如:
byte[2], 型别占用内存,即byte型别占用1byte,数组长度为2,这样占用的总内存为1 * 2 + 16 + 8 = 26,padding上6bytes,26 + 6 = 32bytes,所以byte[2]占用内存为32bytes

七. 多维数组和一维对象数组
——————————————————————————————–
1. 在32位的系统中, 占用内存为: reference占用内存 * 数组第1维长度 +12(数组本身被当做reference占8bytes,数组长度占4bytes)。如:
byte[3][7], reference占用内存4byte,数组第1维长度为3,这样占用的总内存为4 * 3 + 12 = 24,所以byte[3][7]占用内存为24bytes。再如byte[7][3], reference占用内存4byte,数组第1维长度为7,这样占用的总内存为4 * 7 + 12 = 40,所以byte[7][3]占用内存为40bytes。再如new HashMap[7][6][4],reference占用内存4byte,数组第1维长度为7,这样占用的总内存为4 * 7 + 12 = 40,所以HashMap[7][6][4]占用内存为40bytes。
2. 在64位的系统中, 占用内存为: reference占用内存 * 数组第1维长度 +24(数组本身被当做reference占16bytes,数组长度占8bytes)。如:
byte[3][7], reference占用内存8byte,数组第1维长度为3,这样占用的总内存为8 * 3 + 24 = 48,所以byte[3][7]占用内存为48bytes。

八. 编码计算
——————————————————————————————–
1. java.lang.instrument.Instrumentation实例由JVM产生,我们需实现一个代理(agent),根据java.lang.instrument的package specification说明,这个代理里需有个public static void premain(String agentArgs, Instrumentation inst); 方法,这样在JVM初始化后在调用应用程序main方法前,JVM将调用我们agent里的这个premain方法,这样就注入了Instrumentation实例。
2. 计算实例的内存大小,通过Instrumentation#getObjectSize(Object objectToSize)获得。
3. 注意: 如果有field是常量(如, Boolean.FALSE),因为多实例共享,所以算其占用内存为0。
4. 如计算对象Deep范围内存占用的话则需递归计算引用对象占用的内存,然后进行累加。
5. 代码实现如下MemoryCalculator.java:

/*
 * @(#)MemoryCalculator.java	1.0 2010-11-8
 *
 * Copyright 2010 Richard Chen(utopia_rabbi@sse.buaa.edu.cn) All Rights Reserved.
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package charpter.memory;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Stack;

/**
 * 提供实例占用内存大小的计算功能. 内部借助JVM的{@link Instrumentation}实现.
 *
 * @author Rich, 2010-11-8.
 * @version 1.0
 * @since 1.0
 */
public final class MemoryCalculator {
	/**
	 * JVM在初始化后在调用应用程序main方法前将调用本方法, 本方法中可以写任何main方法中可写的代码.
	 *
	 * @param agentArgs 命令行传进行来的代理参数, 内部需自行解析.
	 * @param inst JVM注入的句柄.
	 */
	public static void premain(String agentArgs, Instrumentation inst) {
		instrumentation = inst;
	}
	/**
	 * 计算实例本身占用的内存大小. 注意:
	 * 1. 多次调用可能结果不一样, 主要跟实例的状态有关
	 * 2. 实例中成员变量如果是reference类型, 则reference所指向的实例占用内存大小不统计在内
	 *
	 * @param obj 待计算内存占用大小的实例.
	 * @return 内存占用大小, 单位为byte.
	 */
	public static long shallowSizeOf(Object obj) {
		if (instrumentation == null) {
			throw new IllegalStateException("Instrumentation initialize failed");
		}
		if (isSharedObj(obj)) {
			return 0;
		}
		return instrumentation.getObjectSize(obj);
	}
	/**
	 * 计算实例占用的内存大小, 含其成员变量所引用的实例, 递归计算.
	 *
	 * @param obj 待计算内存占用大小的实例.
	 * @return 内存占用大小, 单位为byte.
	 */
	public static long deepSizeOf(Object obj) {
		Map calculated = new IdentityHashMap();
		Stack unCalculated = new Stack();
		unCalculated.push(obj);
		long result = 0;
		do {
			result += doSizeOf(unCalculated, calculated);
		} while (!unCalculated.isEmpty());
		return result;
	}
	/**
	 * 判断obj是否是共享对象. 有些对象, 如interned Strings, Boolean.FALSE和Integer#valueOf()等.
	 *
	 * @param obj 待判断的对象.
	 * @return true, 是共享对象, 否则返回false.
	 */
	private static boolean isSharedObj(Object obj) {
		if (obj instanceof Comparable) {
			if (obj instanceof Enum) {
				return true;
			} else if (obj instanceof String) {
				return (obj == ((String) obj).intern());
			} else if (obj instanceof Boolean) {
				return (obj == Boolean.TRUE || obj == Boolean.FALSE);
			} else if (obj instanceof Integer) {
				return (obj == Integer.valueOf((Integer) obj));
			} else if (obj instanceof Short) {
				return (obj == Short.valueOf((Short) obj));
			} else if (obj instanceof Byte) {
				return (obj == Byte.valueOf((Byte) obj));
			} else if (obj instanceof Long) {
				return (obj == Long.valueOf((Long) obj));
			} else if (obj instanceof Character) {
				return (obj == Character.valueOf((Character) obj));
			}
		}
		return false;
	}
	/**
	 * 确认是否需计算obj的内存占用, 部分情况下无需计算.
	 *
	 * @param obj 待判断的对象.
	 * @param calculated 已计算过的对象.
	 * @return true, 意指无需计算, 否则返回false.
	 */
	private static boolean isEscaped(Object obj, Map calculated) {
		return obj == null || calculated.containsKey(obj)
				|| isSharedObj(obj);
	}
	/**
	 * 计算栈顶对象本身的内存占用.
	 *
	 * @param unCalculated 待计算内存占用的对象栈.
	 * @param calculated 对象图谱中已计算过的对象.
	 * @return 栈顶对象本身的内存占用, 单位为byte.
	 */
	private static long doSizeOf(Stack unCalculated, Map calculated) {
		Object obj = unCalculated.pop();
		if (isEscaped(obj, calculated)) {
			return 0;
		}
		Class clazz = obj.getClass();
		if (clazz.isArray()) {
			doArraySizeOf(clazz, obj, unCalculated);
		} else {
			while (clazz != null) {
				Field[] fields = clazz.getDeclaredFields();
				for (Field field : fields) {
					if (!Modifier.isStatic(field.getModifiers())
							&& !field.getType().isPrimitive()) {
						field.setAccessible(true);
						try {
							unCalculated.add(field.get(obj));
						} catch (IllegalAccessException ex) {
							throw new RuntimeException(ex);
						}
					}
				}
				clazz = clazz.getSuperclass();
			}
		}
		calculated.put(obj, null);
		return shallowSizeOf(obj);
	}
	/**
	 * 将数组中的所有元素加入到待计算内存占用的栈中, 等待处理.
	 *
	 * @param arrayClazz 数组的型别.
	 * @param array 数组实例.
	 * @param unCalculated 待计算内存占用的对象栈.
	 */
	private static void doArraySizeOf(Class arrayClazz, Object array,
			Stack unCalculated) {
		if (!arrayClazz.getComponentType().isPrimitive()) {
			int length = Array.getLength(array);
			for (int i = 0; i < length; i++) {
				unCalculated.add(Array.get(array, i));
			}
		}
	}
	/** JVM将在启动时通过{@link #premain}初始化此成员变量. */
	private static Instrumentation instrumentation = null;
}


,从Java SE 6u23之后的64位版本就默认打开了对象指针压缩。

十. Compressed oops的内存占用
注意,Compressed oops只在64位的JVM中才会有,另外,在Java SE 6u23之前的1.6版本中需要通过-XX:+UseCompressedOops参数开启。压缩算法对对象内存占用计算的影响主要在于:
——————————————————————————————–
1. object header,未压缩前由一个native-sized mark word 8bytes加上一个class word 8bytes组成,共16bytes。采用压缩后,class word缩减为4bytes,现共占用12bytes;
2. reference类型,由8bytes缩减为4bytes;
3. 数组长度,由8bytes缩减为4bytes。

所以,上述测试案例中:
——————————————————————————————–
1. 原生类型,内存占用大小不变。
2. 对象类型,object header由16bytes变更为12bytes,reference类型的fields由8bytes变更为4bytes,primitive类型的fields保持不变,padding不变。
3. 一维原生数组,如new byte[2]占用内存的计算公式由:型别占用内存 * 数组长度 + 16 + 8 + padding变更为: 型别占用内存 * 数组长度 + 12 + 4 + padding,这样得到: 1byte * 2 + 12 + 4 = 18,padding上6bytes等于24bytes。
4. 多维数组和一维对象数组,如new byte[3][7],计算公式由: reference占用内存 * 数组第1维长度 +24(数组本身被当做reference占16bytes,数组长度占8bytes) 变更为: reference占用内存 * 数组第1维长度 + 16(object header 12bytes,数组长度占4bytes) + padding,这样得到:4bytes * 3 + 16 = 28,padding上4bytes等于32bytes。 再如new HashMap[7],7 * 4bytes + 16 = 44bytes,padding上4bytes为48bytes。

十一. 总结
通过上述Java内存占用大小的理论分析与实际测试,给我们实际开发带来几点重要的启发:
——————————————————————————————–
1. 同样的程序在不同环境下运行,占用的内存不一样大小,64位系统上占用的内存要比在32位系统上多1至1.5倍;
2. n个元素的数组要比n个单独元素占用更大的内存,特别是primitive类型的数组;
3. 定义多维数组时,要尽可能把长度小的放在第1维,即int[9][1]要比int[1][9]占用更多内存,Integer[1000][4][3]远比Integer[3][4][1000]占用的内存要多得多;
4. Java SE 6u23之后的64位版本要比之前的版本在对象内存占用方面小得多。

 

  1. jvm对于对象会启用对齐优化,我们定义类时field的顺序在运行期会被打乱

  2. 关闭了压缩指针模式后,Person对象体偏移由 offset = 16变成了 offset = 12

 

所以开启压缩指针模式后,对象头的_klass域得到了压缩,居然变成了32位系统时的长度4字节了,我们都知道32位的长度最多只能表示4G的内存,那么HostSpot 究竟是如何处理的呢

我们引用官方文档:

Java HotSpot? Virtual Machine Performance Enhancements

这就是面对对象的好处,我们面对的最小地址单元不是byte,而是object,也就是说在jvm的世界里32位地址表示的不是4GB,而是4G个对象的指针,大概是32GB,解码过程就是把对象指针乘以8加上GC堆的初始地址就能得到操作系统本地64位地址了,编码过程相反

其中启用压指得有操作系统底层的支持:GC堆从虚拟地址0开始分配

进而我们可以得到压指面对的所有场景:

  • 如果GC堆大小在4G以下,直接砍掉高32位,避免了编码解码过程

  • 如果GC堆大小在4G以上32G以下,则启用UseCompressedOop

  • 如果GC堆大小大于32G,压指失效(所以说服务器内存太大不好......

考虑到内存对齐,Person对象开压指长度为32字节,不开为40字节

分享到:
评论

相关推荐

    测试java对象占用内存大小的例子

    测试java对象占用内存大小的例子,可以测试常用的类型

    JAVA对象所占内存大小计算例子

    JAVA对象所占内存大小计算例子,博文地址:http://blog.csdn.net/u012787710/article/details/53164226

    java-sizeof-0.0.4:一个查看java对象占用内存大小

    java-sizeof-0.0.4:一个查看java对象占用内存大小

    java 对象 内存 大小

    有效测量出 java 对象 内存 大小 可供缓存等计算

    统计缓存(java对象所占的内存)大小

    统计缓存大小(查看java对象所占的内存大小).

    为什么Java程序占用的内存比实际分配的多

    要更好的理解你的Java程序将会占用多大的内存需要先了解有哪些因素会影响到内存的占用。这些因素包括:  ● 对象(Objects)  ● 类(Classes)  ● 线程(Theads)  ● 本地数据结构(Native data ...

    Java 对象(数组)占多大空间(几个字节) 手把手做实验

    废话不多说,一起开干 1 前置知识 本次实验基于jdk8 64位以及以上版本。本机环境为jdk11 先查看一下jvm启动的默认参数,里面有2个参数值对本次实验会造成影响。 命令行: java -XX:+PrintCommandLineFlags -version ...

    测定JVM中对象占用内存—SizeOf

    原项目下载地址:...使用说明: 1、将SizeOf.jar放到Eclipse工程路径下,添加到classpath中; 2、运行前添加VM参数:-javaagent:lib/SizeOf.jar 运行即可(将jar放在lib路径下)。

    Java的内存机制(堆和栈)简单理解

    偶然看到一道面试题,Java在实例化一个类的时候,数据在堆和栈中是如何存放的?  public class A{  public int i=1;  public static A a1 = new A();  public static void mian...  Java把内存分为堆内存

    Java虚拟机内存优化实践

    众所周知,Java是从C++的基础上发展而来的,而C++程序的很大的一个问题是内存泄露难以解决,尽管Java的JVM有一套自己的垃圾回收机制来回收内存,在许多情况下并不需要java程序开发人员操太多的心,但也是存在泄露...

    MAT-Memory Analyzer Tool Java内存泄漏分析工具1.5

    MAT 是一个开源的java内存分析工具,能够快速的分析dump文件,可以直观的看到各个对象在内存占用的量大小,以及类实例的数量,对象之间的引用关系,找出对象的GC Roots相关的信息,此外还能生成内存泄露报表,疑似...

    Java创建对象与C++创建对象的比较

     Java和C++都是面向对象的编程语言,然而Java和C++在创建对象时却存在不同的方式,由于方式的不同导致在内存中管理的不同。  1、C++创建对象方式  在C++中我们可以采用如下两种方式来创建对象, 1 Dog ...

    Tomcat内存泄露问题排查

    使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。 当服务器应用占用了过多内存的时候,会...

    Java八股文的面试题

    垃圾回收(GC): Java通过垃圾回收机制自动管理内存,开发者无需手动释放对象占用的内存。常见的垃圾回收算法包括标记-清除、复制、标记-整理等。 Java集合框架(JCF): Java集合框架提供了一套性能优化的接口和类,...

    Java程序设计基础:创建String字符串.pptx

    字符串常量:双引号括起来的多个字符组成的序列,每一个字符占2个字节,在内存中共占用2n个字节,n为字符的个数。 例如: " 1234 " , " hello world " ,"A " // 正确 ‘1234’ ,‘abcd’ //错误 创建String对象 ...

    JAVA性能瓶颈和漏洞检测

    JProbe Memory Debugger可帮助开发人员快速查找Java代码的内存泄露和对象循环。内置的图形化实时内存使用和对象视图,有助于开发人员理解应用的内存使用,设法减少内存消耗以提高应用性能。 主要功能: 识别内存...

    MAT工具让你像专家一样分析和解决Java内存问题

    MAT(Memory Analyzer Tool),一个基于...使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。

    从 Clojure测量对象内存消耗_Clojure_代码_相关文件_下载

    它允许在运行时检查对象及其所有子字段占用的内存量。 与jamm相比的额外功能: 轻松的运行时加载(您可以在 REPL 中随时开始使用它,无需提供额外的启动参数)。 人类可读的大小输出。 jamm JAR 文件与clj-memory-...

    MAT(Memory Analyzer Tool)内存分析工具的安装与使用

    使用MAT分析Java堆快照,可以快速计算出对象的保留大小(Retained Sizes),查找到阻止对象被回收的原因,MAT会自动生成一个包含内存泄漏疑点的报告。 MAT是有两种安装方式的,这一点与其他eclipse插件略有不同。 ...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    这些对象分别占用不同的内存空间,因此改变其中一个对象的状 态不会影响其它对象的状态 。 3.初始化对象 生成对象的最后一步是执行构造方法,进行初始化。由于对构造方法可以进行重写 ,所以通过给出不同个数或类型...

Global site tag (gtag.js) - Google Analytics