我需要通过循环结果集(Oracle数据库)在内存中存储大量数据。
我进行了一些测试,并且通过使用分析器,我注意到heap size
和used heap
(即我的数据)之间存在相当大的差异。这是一个例子。
我已经看到了可用的JVM参数,以便设置正确的堆大小,但问题是我事先并不知道数据将占用多少字节(因为数据量可能因测试而异) )。
通过观察图像中的图形,问题似乎是执行期间的内存“峰值”。
这些峰值是否可以与获取的行数相关(或者通常与提取的数据相关?
有没有办法通过保持内存几乎不变来避免这种影响(这样堆大小不会过度增加)?
由于
答案 0 :(得分:1)
当然你可以限制内存,但这样做并没有太大的好处。如果这样做,垃圾收集将不得不经常发生,这将导致程序执行速度变慢。
这就是垃圾收集在Java中的工作方式。如果你有足够的内存GC将不会被调用。这为您的应用程序提供了更多资源(CPU时间)。
此外,为了优化内存消耗,您应该检查算法并查看是否可以重用某些对象而不是创建新对象,因为新对象正是使蓝线上升的原因。请参阅fly weight
以及用于控制内存消耗的其他类似模式。
答案 1 :(得分:1)
通过查看你的内存图表,似乎很多数据属于临时性质,可以在某些时候从堆中删除。使用堆的最终比率与其总大小完全相同。
似乎临时数据(例如,来自Oracle ResultSet
的缓冲数据)生存时间太长或 eden 空间太小,因此数据正在从 eden 和/或幸存者空间到旧一代空间,由于JVM检测到需要运行GC,因此会收集它老一代空间。当您遍历ResultSet
并且Oracle驱动程序需要从数据库中获取下一个数据块(这可能相当大)时,可能会发生这种情况。
此时我应该详细了解Oracle ResultSet
缓冲区。它基本上只是堆上的一大块字节。根据列数据,它存储的内容与您从ResultSet
读取的内容不同。以java.sql.Timestamp
为例。在缓冲区内,它存储为oracle.sql.TIMESTAMP或甚至只是普通字节。这意味着无论何时从java.sql.Timestamp
中提取ResultSet
,都需要分配另一个对象。而且这个对象很可能是你最终要保留的“最终”对象。
我建议根据您的需要调整JVM的GC。也许你可以找出经常收集的数据。尝试调整 eden 大小,以便JVM不需要向老一代提升太多。您还可以调整JVM按需分配的新空间量以及在检测到使用量和分配大小的差距时如何缩小。
您可以找到JVM选项列表here。
答案 2 :(得分:1)
这些峰值是否可以与获取的行数相关(或者通常与提取的数据相关?
我认为你指的是蓝色的山峰。
蓝色区域表示在任何给定时间点使用的内存,峰值表示垃圾收集器运行的点。如您所见,线条与每个峰值成一定角度,然后垂直下降。这是正常行为。
您还会注意到高峰和低谷的高度呈上升趋势。这很可能是应用程序内存数据结构增长的结果。
有没有办法通过保持内存几乎不变来避免这种影响(这样堆大小不会过度增加)?
基本上,没有。如果蓝线没有锯齿状,或者峰值较浅且靠得更近,那就意味着GC运行频率更高......这对性能不利。
基本上,如果你在内存中构建一个大数据结构,你需要足够的内存来代表它,为临时对象增加一些额外的空间,并让垃圾收集器空间去做它需要做的事情。
如果你担心你的应用程序整体上使用了太多内存,那么你需要优化你正在构建的内存数据结构,并检查你是否有任何(其他)内存泄漏。
如果您担心无法预测Java堆需要多大,那么请考虑先将SQL查询作为COUNT运行,然后根据计数以堆大小估计值启动/重新启动Java应用程序