例如,让's say I have an application that can read in a CSV file with piles of data rows. I give the user a summary of the number of rows based on types of data, but I want to make sure that I don' t读入太多数据行并导致 OutOfMemoryError
s . 每行都转换为一个对象 . 有没有一种简单的方法可以通过编程方式找出该对象的大小?是否有一个引用定义了 VM
的原始类型和对象引用的大小?
现在,我的代码说读到了 32,000 rows ,但我'd also like to have code that says read as many rows as possible until I'已经使用了 32MB 的内存 . 也许这是一个不同的问题,但我仍然想知道 .
24 回答
您应该使用jol,这是一个作为OpenJDK项目的一部分开发的工具 .
要获取基元,引用和数组元素的大小,请使用
VMSupport.vmDetails()
. 在64位Windows上运行的Oracle JDK 1.8.0_40上(用于以下所有示例),此方法返回您可以使用
ClassLayout.parseClass(Foo.class).toPrintable()
(可选地将实例传递给toPrintable
)获取对象实例的浅层大小 . 这只是该类的单个实例所占用的空间;它不包括该类引用的任何其他对象 . 它确实包括对象头,字段对齐和填充的VM开销 . 对于java.util.regex.Pattern
:您可以使用
GraphLayout.parseInstance(obj).toFootprint()
获取对象实例的深度大小的摘要视图 . 当然,足迹中的一些对象可能是共享的(也是从其他对象引用的),因此当该对象被垃圾收集时,它可以被回收的空间过于活跃 . 对于Pattern.compile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
的结果(取自this answer),jol报告的总占用空间为1840字节,其中只有72个是Pattern实例本身 .如果您改为使用
GraphLayout.parseInstance(obj).toPrintable()
,jol将告诉您每个引用对象的字段解引用的地址,大小,类型,值和路径,尽管这通常太详细而无用 . 对于正在进行的模式示例,您可能会得到以下结果 . (地址可能会在运行之间发生变化 . )"(something else)"条目describe other objects in the heap that are not part of this object graph .
最好的jol文档是jol存储库中的jol samples . 这些示例展示了常见的jol操作,并展示了如何使用jol来分析VM和垃圾收集器内部 .
我不小心发现了一个java类“jdk.nashorn.internal.ir.debug.ObjectSizeCalculator”,它已经在jdk中,它易于使用,对于确定对象的大小似乎非常有用 .
结果:
您可以生成堆转储(例如,使用jmap),然后分析输出以查找对象大小 . 这是一个离线解决方案,但您可以检查浅和深的尺寸等 .
许多其他答案提供浅尺寸 - 例如没有任何键或值的HashMap的大小,这可能不是你想要的 .
jamm项目使用上面的java.lang.instrumentation包,但是遍历树,因此可以为您提供深度内存使用 .
https://github.com/jbellis/jamm
我写了一个快速测试,以便即时估算:
一般概念是分配对象并测量空闲堆空间中的更改 . 关键是
getFreeMemory()
,其中 requests GC runs and waits for the reported free heap size to stabilize . 以上的输出是:考虑到对齐行为和可能的堆块头开销,我们期望这是什么 .
这里接受的答案中详述的仪器方法最准确 . 我描述的方法是准确的,但只能在没有其他线程创建/丢弃对象的受控条件下 .
您必须使用工具进行测量,或者手动估算它,这取决于您使用的JVM .
每个对象有一些固定的开销 . 它是特定于JVM的,但我通常估计40个字节 . 然后你必须看看班上的成员 . 对象引用在32位(64位)JVM中是4(8)个字节 . 原始类型是:
布尔和字节:1个字节
char和short:2个字节
int和float:4个字节
长和双:8个字节
数组遵循相同的规则;也就是说,它是一个对象引用,因此在对象中占用4(或8)个字节,然后将其长度乘以其元素的大小 .
尝试通过调用
Runtime.freeMemory()
以编程方式执行此操作只是因为对垃圾收集器的异步调用等而无法提供更多准确性 . 使用-Xrunhprof或其他工具对堆进行分析将为您提供最准确的结果 .你可以使用java.lang.instrument package
编译并将此类放在JAR中:
将以下内容添加到
MANIFEST.MF
:使用getObjectSize:
调用:
首先,“对象的大小”在Java中并不是一个明确定义的概念 . 你可以指对象本身,只有它的成员,对象和它引用的所有对象(参考图) . 您可以指内存大小或磁盘大小 . 并允许JVM优化Strings之类的东西 .
所以唯一正确的方法是向JVM询问一个好的分析器(我使用YourKit),这可能不是你想要的 .
但是,从上面的描述中可以看出每行都是自包含的,并且没有大的依赖树,因此序列化方法可能是大多数JVM的良好近似 . 最简单的方法如下:
请记住,如果您有具有公共引用的对象,则不会给出正确的结果,并且序列化的大小将不总是与内存中的大小匹配,但它是一个很好的近似值 . 如果将ByteArrayOutputStream大小初始化为合理值,则代码将更有效 .
如果您只想知道JVM中使用了多少内存,以及多少是免费的,您可以尝试这样的方法:
编辑:我认为这可能会有所帮助,因为问题作者还表示他希望有一个逻辑处理“在我使用32MB内存之前尽可能多地读取行” .
只需使用java visual VM .
它具有分析和调试内存问题所需的一切 .
它还有一个OQL(对象查询语言)控制台,允许你做很多有用的事情,其中一个是
sizeof(o)
几年前,Javaworld有了an article on determining the size of composite and potentially nested Java objects,他们基本上都是在Java中创建一个sizeof()实现 . 该方法基本上 Build 在其他工作的基础上,其中人们通过实验确定基元和典型Java对象的大小,然后将该知识应用于递归遍历对象图以计算总大小的方法 .
它总是比原生C实现更准确,仅仅是因为类的幕后发生的事情,但它应该是一个很好的指标 .
或者是SourceForge适当调用sizeof的项目,它提供了一个带有sizeof()实现的Java5库 .
附:不要使用序列化方法,序列化对象的大小与它在活动时消耗的内存量之间没有关联 .
我怀疑你想以编程方式进行,除非你只是想做一次并存储它以备将来使用 . 这是一件很昂贵的事情 . Java中没有sizeof()运算符,即使存在,也只计算对其他对象的引用和基元大小的成本 .
你可以做的一种方法是将事物序列化为文件并查看文件的大小,如下所示:
当然,这假设每个对象都是不同的,并且不包含对其他任何东西的非瞬态引用 .
另一种策略是采用每个对象并通过反射检查其成员并将大小(布尔和字节= 1字节,短和char = 2字节等)相加,沿着成员层次结构向下工作 . 但这很乏味且昂贵,并最终做了序列化策略所做的同样的事情 .
我的回答是基于尼克提供的代码 . 该代码测量序列化对象占用的总字节数 . 所以这实际上测量了序列化的东西普通对象内存占用(只是序列化例如
int
,你会看到序列化字节的总量不是4
) . 因此,如果您希望获得完全用于对象的原始字节数 - 您需要稍微修改该代码 . 像这样:我用原始类型String测试了这个解决方案,并在一些普通的类上测试过 . 可能还没有涵盖的案例 .
UPDATE: 已修改示例以支持数组对象的内存占用计算 .
size会因为对象创建而增加jvm的内存使用量,通常是对象的大小 .
此答案与对象大小无关,但是当您使用数组来容纳对象时;它将为对象分配多少内存大小 .
因此,所有这些集合的数组,列表或映射都不会真正存储对象(仅在基元时,需要实际的对象内存大小),它将仅存储这些对象的引用 .
现在
Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection
PRIMITIVES
OBJECTS
我的意思是说所有对象REFERENCE只需要4个字节的内存 . 它可能是String引用或Double对象引用,但依赖于对象创建所需的内存会有所不同 .
例如)如果我为下面的类
ReferenceMemoryTest
创建对象,那么将创建4 4 4 = 12个字节的内存 . 尝试初始化引用时,内存可能会有所不同 .因此,在创建对象/引用数组时,其所有内容都将被NULL引用占用 . 我们知道每个引用需要4个字节 .
最后,下面代码的内存分配是20个字节 .
ReferenceMemoryTest ref1 = new ReferenceMemoryTest(); (4(ref1)12 = 16字节)ReferenceMemoryTest ref2 = ref1; (4(ref2)16 = 20字节)
还有 Memory Measurer 工具(以前在Google Code,现在在GitHub上),这很简单并且在商业友好型 Apache 2.0 license 下发布,如similar question中所述 .
如果你想测量内存字节消耗,它也需要java解释器的命令行参数,但是看起来工作得很好,至少在我使用它的场景中 .
无需混淆仪器等等,如果您不需要知道对象的字节精确大小,您可以采用以下方法:
这样你就可以在之前和之后读取使用过的内存,并在获取已用内存之前调用GC,将“噪音”降低到几乎为0 .
为了获得更可靠的结果,您可以运行您的作业n次,然后将使用的内存除以n,从而获得一次运行所需的内存量 . 更重要的是,你可以更多次地运行整个事情并取得平均值 .
对于JSONObject,以下代码可以帮助您 .
以字节为单位返回大小
我通过将JSONArray对象写入文件来检查它 . 它给出了对象大小 .
你必须使用反射来对象 . 你要小心:
只是分配一个对象在JVM中有一些开销 . 数量因JVM而异,因此您可以将此值设为参数 . 至少使它成为常量(8个字节?)并应用于任何已分配的内容 .
仅仅因为
byte
理论上是1个字节并不意味着它只需要一个内存 .对象引用中会有循环,所以你需要保持一个
HashMap
或某些使用object-equals作为比较器以消除无限循环 .@jodonnell:我喜欢你的解决方案的简单性,但许多对象不是Serializable(所以这会抛出异常),字段可以是瞬态的,对象可以覆盖标准方法 .
我推荐
carrotsearch
的java-sizeof库 . 这很简单 .你可以在maven中得到它:
只返回一个对象字节的代码行:
你可以在Github上看到源代码
here是图书馆作者的演示文稿 .
java.lang.instrument.Instrumentation
类提供了获取Java对象大小的好方法,但它要求您定义premain
并使用java代理运行程序 . 当您不需要任何代理时,这非常无聊,然后您必须为您的应用程序提供虚拟Jar代理 .所以我使用
sun.misc
中的Unsafe
类得到了另一种解决方案 . 因此,根据处理器体系结构考虑对象堆对齐并计算最大字段偏移量,可以测量Java对象的大小 . 在下面的示例中,我使用辅助类UtilUnsafe
来获取对sun.misc.Unsafe
对象的引用 .没有方法调用,如果这是你要求的 . 通过一些研究,我想你可以写自己的 . 特定实例具有固定大小,其源自引用和原始值的数量加上实例簿记数据 . 您只需走对象图 . 行类型越少,越容易 .
如果这太慢或者只是比它的 Value 更麻烦,那么总是有很好的老式行计数规则 .
回到我在Twitter工作时,我写了一个用于计算深度对象大小的实用程序 . 它考虑了不同的内存模型(32位,压缩的oops,64位),填充,子类填充,在圆形数据结构和数组上正常工作 . 你可以编译这个.java文件;它没有外部依赖:
https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java
这是我使用一些链接的示例来处理32位,64位和64位压缩OOP的实用程序 . 它使用
sun.misc.Unsafe
.它使用
Unsafe.addressSize()
获取本机指针的大小,使用Unsafe.arrayIndexScale( Object[].class )
获取Java引用的大小 .它使用已知类的字段偏移量来计算对象的基本大小 .