转自:http://article.yeeyan.org/view/104091/62930
再议如何确定Java对象的大小
vangie于2009-10-10 22:42:28翻译 | 已有2346人浏览 | 有0人评论
在JDK 5.0之前,想要精确的计算Java对象在内存中的的大小并不容易,要么使用原始的统计方法,要么使用很影响性能的大型工具。而Instrumentation API可以借助于Java原生方法很好的解决这一问题。
Tags:Instrumentation | getObjectSize
再议如何确定Java对象的大小
有时计算java对象在内存中的大小是必要的。这篇文章将介绍一种借助于Java Instrumentation API的方法。
适用场景
计算java对象大小适用于如下场景
缓存 缓存通常用来提高频繁访问数据的性能。由于受到Java进程可分配内存大小的限制,它们通常不能从数据库(或者其他存储)中加载所有数据。也就是说,为了确保其内存大小不超过预设,缓存必须计算已加载数据的大小并且丢弃旧数据。
检测内存泄漏 在某些情况下你能在存在泄漏的指令的前后量测堆内存的大小并发现内存泄漏。如果你怀疑某些对象存在泄漏,你需要精确量测他们的大小并和泄漏的内存进行比较。有许多专门的大工具用可供使用,但是他们通常都太重量级了,并影响性能。在某些情况下如果有一种简单的对象大小计算方法,你能够更快的解决问题。
其他内存估算 例如,你能估算JVM最大堆内存的设置,如果你知道有多少对象将在你的应用程序中创建。
纯属娱乐:)
常见方法概述
有几种不同的方法可用于确定java对象的大小。他们大多是在JDK 5.0之前已经出现。
http://jroller.com/page/mipsJava?entry=sizeof_java_objects -使用System.gc(),Runtime.freeMemory(), Runtime.totalMemory()方法来计算java对象的大小。这个方法通常需要许多资源才能精确计算出对象的大小。它必须创建许多的需要估算对象的实例(最好是几千个),在创建的前后量测堆内存的大小。这个方法对于使用缓存机制的生产系统并不奏效。这个方法的优点是可以得到较为精确的结果,而不受Java实现版本和操作系统的影响。
另一个更好的方法:http://www.javaspecialists.co.za/archive/Issue078.html - 它更加的巧妙。他使用真实的原始类型大小的对照表来确定整个对象的大小。使用反射API遍历对象继承链上的成员变量并且计算所有原始类型变量的大小。这个方法不像上一方法那样需要很多的资源并能够用于缓存机制。弊端是原始类型大小的对照表会随着JVM实现版本的不同而不同,对于不同的实现版本需要重新计算。
下面是一些关于类似方法的文章:
http://www.javaworld.com/javaworld/javatips/jw-javatip130.html
http://www.javaworld.com/javaworld/javaqa/2003-12/02-qa-1226-sizeof.html
http://www.javapractices.com/Topic83.cjp
http://forum.java.sun.com/thread.jspa?threadID=565721&messageID=2790847
使用Instrumentation API确定java对象大小
从JDK 5.0开始,新引入的 Instrumentation API 终于提供了 getObjectSize 方法。但是使用这个方法有两个问题:
这个方法不能直接使用,必须实现一个instrumentation代理类并且打包进JAR文件。
它仅返回某个对象的大小而不包括其成员变量所引用的对象。
这些问题很容易被解决。在任何类中,可以通过声明premain方法实现Java代理类:
1: public class SizeOfAgent {
2:
3: static Instrumentation inst;
4:
5: /** initializes agent */
6: public static void premain(String agentArgs, Instrumentation instP) {
7: inst = instP;
8: }
9: }permain方法会在启动的时候被JVM调用,Instrumentation的实例被传入。SizeOfAgent类使用静态变量保存Instrumentation对象的引用。为了让JVM知道instrumentation代理类的存在,必须将其打包进JAR文件并且设定manifest.mf文件中的属性。在我们的例子中,需要设定如下属性:
Premain-Class: sizeof.agent.SizeOfAgent
Boot-Class-Path:
Can-Redefine-Classes: false
另外,Java程序必须使用 -javaagent 参数指向该jar文件来启动。我们的例子中形如:
java -javaagent:sizeofag.jar <Your main class>
在获得了Instrumentation对象的引用之后,实现一个sizeOf方法变得很简单。
1: public class SizeOfAgent {
2:
3: static Instrumentation inst;
4:
5: // ...
6:
7: public static long sizeOf(Object o) {
8: return inst.getObjectSize(o);
9: }
10: }
SizeOgAgent.sizeOf()方法能方便的被你的程序所调用。如前面所提到的,这个方法仅返回某个对象的大小而不包括其成员变量。完整的对象大小能通过反射得到。我们能通过简单的递归遍历所有的成员变量来总计他们的大小。并不是所有人都知道能够通过反射访问private和protected变量。你仅仅需要在获得private成员之前调用 Field.setAccessible(true) 方法即可。下面是SizeOfAgent类中fullSizeOf方法完整实现的源码:
001: package sizeof.agent;
002:
003: import java.lang.instrument.Instrumentation;
004: import java.lang.reflect.Array;
005: import java.lang.reflect.Field;
006: import java.lang.reflect.Modifier;
007: import java.util.IdentityHashMap;
008: import java.util.Map;
009: import java.util.Stack;
010:
011: /** Instrumentation agent used */
012: public class SizeOfAgent {
013:
014: static Instrumentation inst;
015:
016: /** initializes agent */
017: public static void premain(String agentArgs, Instrumentation instP) {
018: inst = instP;
019: }
020:
021: /**
022: * Returns object size without member sub-objects.
023: * @param o object to get size of
024: * @return object size
025: */
026: public static long sizeOf(Object o) {
027: if(inst == null) {
028: throw new IllegalStateException("Can not access instrumentation environment.n" +
029: "Please check if jar file containing SizeOfAgent class is n" +
030: "specified in the java's "-javaagent" command line argument.");
031: }
032: return inst.getObjectSize(o);
033: }
034:
035: /**
036: * Calculates full size of object iterating over
037: * its hierarchy graph.
038: * @param obj object to calculate size of
039: * @return object size
040: */
041: public static long fullSizeOf(Object obj) {
042: MapObject, Object> visited = new IdentityHashMapObject, Object>();
043: StackObject> stack = new StackObject>();
044:
045: long result = internalSizeOf(obj, stack, visited);
046: while (!stack.isEmpty()) {
047: result += internalSizeOf(stack.pop(), stack, visited);
048: }
049: visited.clear();
050: return result;
051: }
052:
053: private static boolean skipObject(Object obj, MapObject, Object> visited) {
054: if (obj instanceof String) {
055: // skip interned string
056: if (obj == ((String) obj).intern()) {
057: return true;
058: }
059: }
060: return (obj == null) // skip visited object
061: || visited.containsKey(obj);
062: }
063:
064: private static long internalSizeOf(Object obj, StackObject> stack, MapObject, Object> visited) {
065: if (skipObject(obj, visited)){
066: return 0;
067: }
068: visited.put(obj, null);
069:
070: long result = 0;
071: // get size of object + primitive variables + member pointers
072: result += SizeOfAgent.sizeOf(obj);
073:
074: // process all array elements
075: Class clazz = obj.getClass();
076: if (clazz.isArray()) {
077: if(clazz.getName().length() != 2) {// skip primitive type array
078: int length = Array.getLength(obj);
079: for (int i = 0; i length; i++) {
080: stack.add(Array.get(obj, i));
081: }
082: }
083: return result;
084: }
085:
086: // process all fields of the object
087: while (clazz != null) {
088: Field[] fields = clazz.getDeclaredFields();
089: for (int i = 0; i fields.length; i++) {
090: if (!Modifier.isStatic(fields[i].getModifiers())) {
091: if (fields[i].getType().isPrimitive()) {
092: continue; // skip primitive fields
093: } else {
094: fields[i].setAccessible(true);
095: try {
096: // objects to be estimated are put to stack
097: Object objectToAdd = fields[i].get(obj);
098: if (objectToAdd != null) {
099: stack.add(objectToAdd);
100: }
101: } catch (IllegalAccessException ex) {
102: assert false;
103: }
104: }
105: }
106: }
107: clazz = clazz.getSuperclass();
108: }
109: return result;
110: }
111: }基本思想类似于Dr. Heinz M. Kabutz的方法:http://www.javaspecialists.co.za/archive/Issue078.html.我甚至重用了他的skipObject方法。该算法用来保证每个对象仅被统计一次,防止循环引用。另外它忽略了intern类型的String(详细参加 String.intern())。
缺点
这个方法的主要缺点是不能用于沙箱环境类似于applet或者Web Start程序。这个限制是因为通过反射访问私有成员的方法和instrumentation代理在沙箱环境中无效。
文件
文件 sizeofag.jar 包含编译好的class文件和java源码。你可以仅仅将sizeofag.jar通过-javaagent参数添加进JVM中,在你的程序中像使用普通类一样使用SizeOfAgent类。好好享受它吧:)
分享到:
相关推荐
总结来说,理解Java对象大小的计算对于优化内存使用、防止内存泄漏以及提高应用程序性能至关重要。开发人员需要了解不同方法的优缺点,并根据实际需求选择合适的方式。同时,应当注意,过于关注单个对象的大小可能...
但是,Java API本身并不提供直接获取对象大小的方法。为了计算对象大小,我们需要借助一些第三方库或者自行实现。 一个常见的第三方库是`DeepSizeOf`,它来自Eclipse Collections框架,提供了类似于C++中的`sizeof`...
本示例“测量Java对象大小的demo”提供了一种方法,通过使用Java的`java.lang.instrument`包来注入`javaagent`,进而利用`Instrumentation`接口测量Java对象的精确内存占用。下面我们将详细探讨这一过程。 首先,`...
当我们谈论“Java对象内存大小”时,我们通常指的是一个Java对象在内存中占据的空间,包括对象头、实例字段以及可能的对齐填充。这个知识点对于开发高效缓存系统尤其重要,因为缓存需要精确管理内存来最大化存储效率...
本文将深入探讨如何计算Java对象的大小,以及这个知识点在实际开发中的应用。 首先,Java对象的大小不仅仅包括其字段的大小,还包括对象头(object header)的大小,对于HotSpot虚拟机,它包含了对齐填充、Mark ...
本文将深入探讨如何计算Java对象所占内存,并通过提供的代码示例进行详细解析。 首先,我们需要理解Java对象内存占用的基本原理。每个Java对象都由三部分组成:对象头(Object Header)、实例数据(Instance Data)...
本文将深入探讨如何统计缓存(尤其是Java对象)所占的内存大小,以及这对理解程序内存消耗的重要性。 首先,我们要知道Java对象的内存开销主要由三部分组成:对象头、实例数据和对齐填充。对象头包含对象的类型信息...
本篇文章将深入探讨如何计算Java对象占用的内存字节数,以及影响这一数值的因素。 首先,Java对象在堆内存中由四个部分组成:对象头(A)、基本类型域(B)、引用类型域(C)和填充物(D)。 **对象头(A)**: ...
例如,`sun.misc.Unsafe`类(虽然非标准,但广泛使用)提供了访问内存的能力,包括获取对象大小。然而,需要注意的是,这个类在Java 9及以后的版本中被弃用,并且在某些JRE中可能不可用。 `SizeOfObject.java`可能...
NULL 博文链接:https://spice.iteye.com/blog/1104340
本篇文章将深入探讨Java对象池的实现原理,以及如何借鉴"Jakarta Commons Pool"组件来设计一个轻量级的对象池。 一、对象池的基本概念 对象池的基本工作流程包括以下几个步骤: 1. 初始化:预创建一定数量的对象并...
本文将深入探讨Java中的对象内存占用,以及如何使用"java-sizeof-0.0.4"工具来查看Java对象在内存中的大小。 在Java中,内存主要分为堆内存(Heap)和栈内存(Stack)。对象通常存储在堆内存中,而基本类型的变量和...
### Java对象创建过程详解 在Java编程语言中,对象是程序的基本单元,一切皆对象这一概念使得Java在面向对象编程领域具有重要的地位。本文将详细阐述Java对象的创建过程,帮助读者深入理解Java基础。 #### 一、类...
NULL 博文链接:https://langyu.iteye.com/blog/1167581
Java 面向对象编程练习题与答案 本资源摘要信息涵盖了 Java 面向对象编程的多个知识点,包括 Font 类、Toolkit 类、包机制、抽象类、数组操作等。 1. Font 类: Font 类是 Java 中的一个类,用于设置程序中的字体...
本篇文章将深入探讨Java中的对象、基础类型以及数据处理相关的工具类。 首先,让我们关注Java中的基础类型。Java有八种原始数据类型:byte、short、int、long、float、double、char和boolean。为了方便处理这些类型...
本篇文章将深入探讨Java对象在JVM内存中的布局,帮助我们理解JVM是如何存储和管理对象的。 首先,我们要知道JVM内存主要分为以下几个区域: 1. **堆内存(Heap)**:这是Java对象的主要存储区域,所有通过`new`...
总之,Java对象和数组的内存占用取决于JVM的配置、对象的字段、数组的元素类型和长度等因素。开启或关闭压缩选项会影响对象和数组引用的大小,进而影响整体的内存占用。对于内存优化和性能调优来说,了解这些细节至...
【小知识】第4期_Java对象大小怎么算
然而,栈的局限性也很明显,即存储在栈中的数据大小和生存周期必须是确定的,这意味着它缺乏堆所提供的灵活性。此外,栈中的数据可以共享,这一点将在后续部分详细讨论。 #### 堆的灵活性与代价 与栈相比,堆的...