从 csv 文件行创建 Java objects 的开销是多少
What is an overhead for creating Java objects from lines of csv file
代码读取 CSV 文件的行,如:
Stream<String> strings = Files.lines(Paths.get(filePath))
然后它映射映射器中的每一行:
List<String> tokens = line.split(",");
return new UserModel(tokens.get(0), tokens.get(1), tokens.get(2), tokens.get(3));
终于收藏了:
Set<UserModel> current = currentStream.collect(toSet())
文件大小约为 500MB
我已经使用 jconsole 连接到服务器,并看到在处理过程中堆大小从 200MB 增加到 1.8GB。
我不明白这个 x3 内存使用量从何而来 - 我预计会有 500MB 左右的峰值?
我的第一印象是因为没有节流,垃圾收集器根本没有足够的时间进行清理。
但我尝试使用番石榴速率限制器让垃圾收集器有时间完成它的工作,但结果是一样的。
您的代码将整个文件读入内存。然后开始将每一行拆分为一个数组,然后为每一行创建自定义 class 的对象。所以基本上你的文件中的每一行都有 3 个不同的 "memory usage" !
虽然有足够的内存可用,但 jvm 可能根本不会浪费时间 运行 垃圾收集器,同时将您的 500 兆字节转换为三种不同的表示形式。因此,您可能 "triplicate" 文件中的字节数。至少在 gc 启动并丢弃不再需要的文件行和拆分数组之前。
可能 String
实现使用的是 UTF-16,而文件可能使用的是 UTF-8。假设所有美国 ASCII 字符,这将是大小的两倍。但是,我相信现在 JVM 倾向于使用 String
s 的紧凑形式。
另一个因素是 Java object 倾向于分配在一个很好的循环地址上。这意味着有额外的填充。
除了支持 char[]
或 byte[]
.
中的实际数据外,还有实际 String
object 的内存
然后是你的 UserModel
object。每个 object 都有一个 header 并且引用通常是 8 字节(可能是 4)。
最后,不会分配所有堆。当相当一部分内存在任何特定时刻未被使用时,GC 运行效率更高。一旦进程启动并且 运行.
即使是 C malloc 也会以大量未使用的内存结束
Tom Hawtin 提出了很好的观点 - 我只是想扩展它们并提供更多细节。
Java 由于 java 对象头(见下文)开销和内部字节数组,字符串至少占用 40 个字节的内存(用于空字符串)。
这意味着 non-empty 字符串(1 个或多个字符)的最小大小为 48 个字节。
如今,JVM 使用 Compact Strings,这意味着 ASCII-only 字符串每个字符仅占用 1 个字节 - 之前每个字符最少占用 2 个字节。
这意味着如果您的文件包含超出 ASCII 集的字符,则内存使用量会显着增加。
与使用 arrays/lists 的普通迭代相比,流也有更多的开销(参见此处 Java 8 stream objects significant memory usage)
我猜你的 UserModel 对象在每一行的顶部至少增加了 32 字节的开销,因为:
- java 对象的最小大小为 16 字节,其中前 12 个字节是 JVM "overhead":对象的 class 引用(Compressed Oops are used) + the Mark word (used for identity hash code, Biased locking 时为 4 字节,垃圾收集器)
- 接下来的 4 个字节由对第一个 "token"
的引用使用
- 接下来的 12 个字节被对第二个、第三个和第四个的 3 个引用使用 "token"
- 最后 4 个字节是必需的,因为 Java Object Alignment 在 8 字节边界(在 64 位架构上)
也就是说,不清楚您是否使用了从文件中读取的所有数据 - 您从一行中解析了 4 个标记,但也许还有更多?
此外,您没有提到堆大小 "grew" 的确切大小 - 如果它是堆的 commited
大小或 used
大小。 used
部分是活动对象实际 "used" 的部分,commited
部分是 JVM 在某个时候分配的部分,但可能是 garbage-collected 之后的部分; used < commited
在大多数情况下。
您必须拍摄堆快照以了解 UserModel
的结果集实际占用了多少内存,与文件的大小进行比较实际上很有趣。
代码读取 CSV 文件的行,如:
Stream<String> strings = Files.lines(Paths.get(filePath))
然后它映射映射器中的每一行:
List<String> tokens = line.split(",");
return new UserModel(tokens.get(0), tokens.get(1), tokens.get(2), tokens.get(3));
终于收藏了:
Set<UserModel> current = currentStream.collect(toSet())
文件大小约为 500MB 我已经使用 jconsole 连接到服务器,并看到在处理过程中堆大小从 200MB 增加到 1.8GB。
我不明白这个 x3 内存使用量从何而来 - 我预计会有 500MB 左右的峰值?
我的第一印象是因为没有节流,垃圾收集器根本没有足够的时间进行清理。 但我尝试使用番石榴速率限制器让垃圾收集器有时间完成它的工作,但结果是一样的。
您的代码将整个文件读入内存。然后开始将每一行拆分为一个数组,然后为每一行创建自定义 class 的对象。所以基本上你的文件中的每一行都有 3 个不同的 "memory usage" !
虽然有足够的内存可用,但 jvm 可能根本不会浪费时间 运行 垃圾收集器,同时将您的 500 兆字节转换为三种不同的表示形式。因此,您可能 "triplicate" 文件中的字节数。至少在 gc 启动并丢弃不再需要的文件行和拆分数组之前。
可能 String
实现使用的是 UTF-16,而文件可能使用的是 UTF-8。假设所有美国 ASCII 字符,这将是大小的两倍。但是,我相信现在 JVM 倾向于使用 String
s 的紧凑形式。
另一个因素是 Java object 倾向于分配在一个很好的循环地址上。这意味着有额外的填充。
除了支持 char[]
或 byte[]
.
String
object 的内存
然后是你的 UserModel
object。每个 object 都有一个 header 并且引用通常是 8 字节(可能是 4)。
最后,不会分配所有堆。当相当一部分内存在任何特定时刻未被使用时,GC 运行效率更高。一旦进程启动并且 运行.
即使是 C malloc 也会以大量未使用的内存结束Tom Hawtin 提出了很好的观点 - 我只是想扩展它们并提供更多细节。
Java 由于 java 对象头(见下文)开销和内部字节数组,字符串至少占用 40 个字节的内存(用于空字符串)。 这意味着 non-empty 字符串(1 个或多个字符)的最小大小为 48 个字节。
如今,JVM 使用 Compact Strings,这意味着 ASCII-only 字符串每个字符仅占用 1 个字节 - 之前每个字符最少占用 2 个字节。 这意味着如果您的文件包含超出 ASCII 集的字符,则内存使用量会显着增加。
与使用 arrays/lists 的普通迭代相比,流也有更多的开销(参见此处 Java 8 stream objects significant memory usage)
我猜你的 UserModel 对象在每一行的顶部至少增加了 32 字节的开销,因为:
- java 对象的最小大小为 16 字节,其中前 12 个字节是 JVM "overhead":对象的 class 引用(Compressed Oops are used) + the Mark word (used for identity hash code, Biased locking 时为 4 字节,垃圾收集器)
- 接下来的 4 个字节由对第一个 "token" 的引用使用
- 接下来的 12 个字节被对第二个、第三个和第四个的 3 个引用使用 "token"
- 最后 4 个字节是必需的,因为 Java Object Alignment 在 8 字节边界(在 64 位架构上)
也就是说,不清楚您是否使用了从文件中读取的所有数据 - 您从一行中解析了 4 个标记,但也许还有更多?
此外,您没有提到堆大小 "grew" 的确切大小 - 如果它是堆的 commited
大小或 used
大小。 used
部分是活动对象实际 "used" 的部分,commited
部分是 JVM 在某个时候分配的部分,但可能是 garbage-collected 之后的部分; used < commited
在大多数情况下。
您必须拍摄堆快照以了解 UserModel
的结果集实际占用了多少内存,与文件的大小进行比较实际上很有趣。