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位版本要比之前的版本在对象内存占用方面小得多。
-
jvm对于对象会启用对齐优化,我们定义类时field的顺序在运行期会被打乱
-
关闭了压缩指针模式后,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对象占用的内存大小,以便更好地理解内存使用情况。 首先,`SizeOf.java`可能是一个实现自定义内存大小计算的类。在Java中,由于垃圾回收机制的存在,直接获取对象的内存占用并不像C++等...
在Java编程语言中,了解一个对象占用的内存大小对于优化程序性能、理解内存消耗以及防止内存泄漏至关重要。本文将深入探讨如何计算Java对象所占内存,并通过提供的代码示例进行详细解析。 首先,我们需要理解Java...
了解对象占用的内存大小有助于我们优化资源使用,减少内存泄漏,并提高程序效率。 "java-sizeof-0.0.4"是一个用于分析Java对象内存占用的工具,它可以帮助开发者更好地理解对象在运行时的内存消耗。这个工具提供了...
当我们谈论“Java对象内存大小”时,我们通常指的是一个Java对象在内存中占据的空间,包括对象头、实例字段以及可能的对齐填充。这个知识点对于开发高效缓存系统尤其重要,因为缓存需要精确管理内存来最大化存储效率...
在Java编程环境中,了解对象占用的内存大小是优化性能的关键步骤。这可以帮助我们避免内存泄漏,提高应用程序的效率。本文将深入探讨如何统计缓存(尤其是Java对象)所占的内存大小,以及这对理解程序内存消耗的重要...
本篇文章将深入探讨如何计算Java对象占用的内存字节数,以及影响这一数值的因素。 首先,Java对象在堆内存中由四个部分组成:对象头(A)、基本类型域(B)、引用类型域(C)和填充物(D)。 **对象头(A)**: ...
这个“Java内存使用系列一Java对象的内存占用”主题旨在深入探讨Java对象在内存中的表现,以及如何有效地管理这些资源。Java开发人员需要理解内存分配、垃圾回收机制以及如何避免内存泄漏,以确保程序的高效运行。 ...
这篇博客文章可能探讨了如何通过不同的工具和技术来估算Java对象在内存中的占用空间。 首先,Java对象的大小不是固定不变的,它取决于对象的类结构,包括类中的属性数量、类型以及虚拟机的实现。每个对象都会有一个...
总之,Java对象和数组的内存占用取决于JVM的配置、对象的字段、数组的元素类型和长度等因素。开启或关闭压缩选项会影响对象和数组引用的大小,进而影响整体的内存占用。对于内存优化和性能调优来说,了解这些细节至...
一个空的String对象占用28个字节的内存,包含了一个指向字符数组的引用、一个偏移量、一个字符的长度和一个哈希码。当字符串内容非空时,内存占用会增加,因为需要存储字符数据。例如,字符串"ab"会占用28 + 2 * 2 =...
本篇文章将深入探讨Java对象在JVM内存中的布局,帮助我们理解JVM是如何存储和管理对象的。 首先,我们要知道JVM内存主要分为以下几个区域: 1. **堆内存(Heap)**:这是Java对象的主要存储区域,所有通过`new`...
在Java编程语言中,了解一个对象的内存大小是很有用的,特别是在性能调优和内存管理方面。`sizeOf`通常是指用来计算对象在内存中占用空间的一种方法。本篇文章将探讨如何通过Java来实现这样的功能,以及相关知识点。...
### Java内存对象分配过程研究 #### 一、引言 Java作为一门强大的面向对象编程语言,在实际开发过程中,对象的创建及其内存管理是至关重要的环节。深入理解对象在内存中的分配过程不仅能够帮助开发者设计出更为...
在Java中,直接获取一个对象所占用的内存大小并非易事。常见的做法是在运行特定代码之前记录当前的内存状态,待代码执行完成后,触发垃圾回收机制,并再次记录内存状态。通过对比两次内存状态的变化,可以估算出代码...
本文介绍了在Java多线程环境下减少内存占用量的一些关键策略,包括线程生命周期管理、对象生命周期设计、同步机制选择、线程池的使用和线程数量控制。同时,代码的异常处理和JVM参数调优也是提升多线程应用性能的...
Java程序在运行过程中占用的内存通常比通过-Xmx和-Xms参数所设定的堆内存大小要多,这是由于Java程序的内存消耗不仅限于堆内存。以下是对各个影响因素的详细解析: 1. **对象(Objects)**:这是Java程序中最基本的...
Java的垃圾回收机制(Garbage Collection, GC)负责自动回收不再使用的对象所占用的堆内存。当一个对象不再有引用指向它时,该对象成为垃圾,GC会在适当的时候将其回收,释放内存。在实验中,虽然没有直接涉及垃圾...
Java对象的内存布局是Java虚拟机(JVM)中至关重要的一部分,它涉及到对象创建、内存分配以及对象访问等核心概念。在Java程序中,我们通常使用`new`关键字创建对象,但这只是表面操作,背后涉及的内存管理和构造器...
8、程序里不可避免大量使用字符串处理,避免使用 String,应大量使用StringBuffer,每一个 String 对象都得独立占用内存一块区域。 在使用字符串时,应该避免使用 String,而应该使用 StringBuffer,以免内存溢出。...
首先,Java对象的大小不仅仅包括其字段的大小,还包括对象头(object header)的大小,对于HotSpot虚拟机,它包含了对齐填充、Mark Word、Klass Pointer等部分。此外,如果对象是数组,还需要考虑数组长度字段。理解...