【问题标题】:Java request taking 40-50MB memory(Spring JPA Hibernate)Java 请求占用 40-50MB 内存(Spring JPA Hibernate)
【发布时间】:2018-03-30 12:42:21
【问题描述】:

我正在使用 Spring Boot 和 JPA Hibernate。 我正在监视 Heap 的服务,发现我的每个请求都占用了大约 40-50 MB。

所以内存增加了,在几次请求 GC 运行后,它释放了内存,这将永远持续下去。

所以我的第一个问题是这是内存泄漏吗?

我也在试图找出造成这种情况的原因。因此,我使用 Runtime.getRuntime() freeMemory 和 totalMemory() 来确定在获取一个 db 调用并使用它填充投影时使用了大约 15MB

public interface RecommendationProjection {
    public String getType();
    public boolean getIsOK();
    public int getId();
    public int getTagCount();
    public double getQuality() ;
    public LocalDateTime getLastActivity();

}

并且休眠返回 567 条记录,所以基本上我从 DB 得到的是上面投影的 567 列表,但我不明白这个对象怎么会占用这么高的内存?是休眠造成的吗?

使用投影时,休眠查询特定字段还是从数据库中获取所有字段?

然后我将此域映射到 DTO,它再次使用 15-20MB 内存? 这是我的 DTO

public class RecommendationInfoDTO {
    private String type;
    private boolean isOK;
    private int id;
    private int tagCount;
    private double quality ;
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss", timezone="IST")
    private LocalDateTime lastActivity;


    .. getters and setters
}

仅供参考:我使用 VisualVM 进行监控。 谁能告诉我可能是什么问题?

我也分析了堆转储但什么都没有?

这是我的堆转储差异。

我在一个请求中触发了 6 个休眠查询和 3 个简单的普通 mysql 查询(使用 jdbc 调用)

问题只是 1 个休眠调用。我认为我的休眠有问题? 有什么方法可以进行基于请求的分析吗?

Gc / 内存图

按大小排序的堆转储

【问题讨论】:

  • 您使用哪个版本的 Java?
  • 您能否发布您获取数据的方式(任何存储库类、自定义查询等)?您应该做的第一件事是确定您的预测是否正常工作。记录您的休眠生成语句,并查看是否选择了所有列或仅选择了投影声明的列。
  • 你能显示根据最后一列(即大小)排序的堆转储吗?
  • 您使用的最小和最大堆大小是多少?您的应用程序运行了多长时间以及您向应用程序发出了多少请求?您是否收到任何 OOM 错误?你能不能也给你看一下GC图。
  • 我使用的是 Java 8,我已经看到查询投影工作正常。查询仅获取特定的记录。最小堆 512M 和最大堆 1024M。我在本地测试它。如此单一(没有任何使用 40MB 的并发请求)

标签: java spring hibernate jpa


【解决方案1】:

在我看来这是正常行为。

所以我的第一个问题是这是内存泄漏吗?

没有。内存泄漏要求内存在超出其使用寿命后仍保持分配状态。由于您的 GC 正在清除查询消耗的总内存空间,因此您没有泄漏,您只是在使用内存。

但是我不明白这个对象怎么会占用这么高的内存?

对象没有占用太多内存,它是对象的 567 个实例每个查询正在占用内存。

让我们来解释一下原因:

您的投影的每个实例都包含

  • 未知长度的String(字符串不是原语,因此在纯字符数的顶部有大量元数据属性,但我们只说 1 个字节)
  • boolean 分配 1 个字节
  • 2 int 每个字节
  • double 2 个字节
  • LocalDateTime,它由几个字段组成(所以让我们乐观一点,说是 2 个字节)

所以每个实例至少 8 个字节。 每个查询最少 567 * 8 = 4536 字节。

您正在针对此数据集发起 6 次查询 每个方法调用 4536 * 6 = 27216 字节

其中一些是休眠中的开销,会在调用之间重复使用,因此您不会看到完整的理论足迹。

这与您观察到的情况相对接近,所以我认为没有任何行为不端。

如果您希望占用更少的空间,请重新评估您的方法以尽可能多地重用数据以减少您必须进行的查询次数。

【讨论】:

  • 我总共使用了 6 个查询。但只有查询正在执行,它获取 567 行。其余查询是简单的查询,不会占用太多内存,因为我已经删除了所有其他并且仍然使用几乎相同的内存。所以它只是一个查询,结果为 567 行。我正在使用投影来获取 6 个特定的列?那么在这种情况下,hibernate 会使用一级缓存吗?如果是,它缓存了什么。我觉得这里唯一的问题是hibernate / spring data jpa正在幕后做smthng。我的查询是使用投影连接 3 个表并获取 6 列的连接查询
  • 一切都需要记忆。有很多事情你可能做错了。你可以打开事务而不关闭它们,你可以池连接,你可以缓存。我们能真正告诉你的是,如果 GC 正在收集所有内容,那么它就不是泄漏。任何进一步的问题都需要作为自己的问题提出。将多个问题叠加在一起通常会导致“过于宽泛”的接近投票。
【解决方案2】:

以下是我的看法:

所以内存增加了,在几次请求 GC 运行后,它释放了内存,这将永远持续下去。

所以我的第一个问题是这是内存泄漏吗?

不一定是内存泄漏。但是您需要运行并运行应用程序更长的时间,并查看在 GC 周期期间内存是如何释放的。只要内存使用率kind of follows a sawtooth pattern 是GC能够回收垃圾并且内存被有效利用的一个指标。

使用投影时,休眠查询特定字段还是从数据库中获取所有字段?

不,在投影的情况下它只获取指定的列。

然后我将此域映射到 DTO,它再次使用 15-20MB 内存?

不仅是您的 DTO,hibernatespring-data-jpa 将在内部创建自己的对象来完成查询,这些对象可能正在等待 GC。只要它们在 GC 循环后被回收,并且每次 GC 后内存使用量没有不断增加,这是一个健康的迹象。

但除了每个请求使用的内存之外,您可能还想看大局,其中一些项目(不是详尽/完整的列表)可能是:

  1. 查看一段时间内正常负载和峰值负载情况下的内存使用情况。
  2. 次要 GC 和主要 GC 发生的频率如何?它对应用程序有何影响?
  3. 应用程序是否在 GC 本身上花费了太多时间?
  4. 根据应用程序行为调整 GC。例如:应用程序是否为服务请求等创建了太多短暂的对象?
  5. 考虑到堆/GC 配置,应用程序是否能够满足您的应用程序响应时间和吞吐量的预期?

最后,您可能想通过java8 GC tuning guide 了解 GC 并对其进行调整。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-12
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    • 2019-07-22
    • 2014-02-25
    • 2019-09-09
    • 1970-01-01
    相关资源
    最近更新 更多