奇怪的Nodejs内存泄漏

8

嘿,我在heroku托管的nodejs服务器上遇到了奇怪的内存泄漏问题。我已经尝试找到这个问题两天了。通过逐个删除我的函数,我发现这个导致问题的函数,但我仍然不确定问题出在哪里。 从heroku的指标中,我可以清楚地看到有内存泄漏。 enter image description here 有人知道为什么这个函数会产生内存泄漏吗?谢谢

completeTicTacToeGame: function (game, winnerIndex, reasonForFinish, userId) {
    if (userId) {
      User.findOne({
        _id: userId
      }, function (err, user) {

        if (err || user == null) {
        }
        else {
          userLivesController.removeUserHeartWithoutResponeAndSaving(user, 'ticTacToeHearts', function (user, canPlay) {
            if (canPlay) {
              dateFormatterController.checkIfDateIsToday(user.lastCompitedTicTacToeGame, function (isToday, isPrevious) {
                var earnCredits = 0
                if (winnerIndex == 0) { earnCredits = 1 }
                if (winnerIndex == 1) { earnCredits = 4 }

                user.credits = user.credits + earnCredits
                user.lifetimeCredits = user.lifetimeCredits + earnCredits


                //increase ad today counter
                user.lastCompitedTicTacToeGame = (new Date()).getTime().toString()

                userBadgesController.checkIfUserNeedToWinBadgeForCredits(user, function (user) {

                  user.save(function (err, user) {
                    if (err) {
                      errorHandlingController.generalSendErrorWithMessage(req, res, 'serverError')
                    }
                    else {
                      var canPlayMoreGames = user.ticTacToeHearts > 0
                      if (earnCredits > 0) {
                        notificationController.sendNotificationToUserForNewTicTacToeGame(user, earnCredits)
                        rankingController.updateRankings(user, earnCredits, 'ticTacToe')
                      }

                      var canWatchAdToDoubleCredits = false
                      if (user.gamesWithoutAd >= ConfigParams.numberOfGamesForAd() && earnCredits > 0) {
                        canWatchAdToDoubleCredits = true
                      }
                      var timeLeftToNextHeart = (((ConfigParams.minutesForHeart() * 60000)) + parseInt(user.lastGivenHeart)) - ((new Date()).getTime())

                      var haveMaxHearts = user.mathGameHearts == ConfigParams.maxHearts() && user.memoryHearts == ConfigParams.maxHearts() && user.ticTacToeHearts == ConfigParams.maxHearts()
                      var canWatchVideoForHeart = true && haveMaxHearts == false
                      if (user.lastWatchedVideoForHeart) {
                        var canWatchVideoForHeart = (((ConfigParams.minutesForHeartWatchedAd() * 60000)) + parseInt(user.lastWatchedVideoForHeart)) - ((new Date()).getTime()) < 0 && haveMaxHearts == false
                      }

                      module.exports.sendMessageToSocketForComlitedGame(game, user._id.toString(), user.credits, earnCredits, canPlayMoreGames, canWatchAdToDoubleCredits, reasonForFinish, user.ticTacToeHearts, ConfigParams.maxHearts, null, user.gamesWithoutAd >= ConfigParams.numberOfGamesForAd() - 1, timeLeftToNextHeart, canWatchVideoForHeart)
                    }
                  })
                })
              })
            }
            else {
              var timeLeftToNextHeart = (((ConfigParams.minutesForHeart() * 60000)) + parseInt(user.lastGivenHeart)) - ((new Date()).getTime())

              var haveMaxHearts = user.mathGameHearts == ConfigParams.maxHearts() && user.memoryHearts == ConfigParams.maxHearts() && user.ticTacToeHearts == ConfigParams.maxHearts()
              var canWatchVideoForHeart = true && haveMaxHearts == false
              if (user.lastWatchedVideoForHeart) {
                var canWatchVideoForHeart = (((ConfigParams.minutesForHeartWatchedAd() * 60000)) + parseInt(user.lastWatchedVideoForHeart)) - ((new Date()).getTime()) < 0 && haveMaxHearts == false
              }

              module.exports.sendMessageToSocketForComlitedGame(game, user._id.toString(), user.credits, 0, false, false, reasonForFinish, user.ticTacToeHearts, ConfigParams.maxHearts, null, user.gamesWithoutAd >= ConfigParams.numberOfGamesForAd() - 1, timeLeftToNextHeart, canWatchVideoForHeart)
            }
          })
        }
      })
    }
  },

你有什么证据让你认为你有内存泄漏? - Pointy
我附上了来自Heroku度量平台的一张图片。您可以看到服务器使用的内存如何逐分钟增加。 - Svetoslav Bramchev
3
垃圾回收以循环的方式进行。对我来说,内存使用看起来相当正常。 - Pointy
您可以看到,内存增加了超过6个小时,这是不正常的。之后我们达到了最大内存并重新启动服务器,这就是为什么内存下降的原因。 - Svetoslav Bramchev
2
你提供的这个函数有很多外部调用者,可能其中一个或几个是泄漏的原因。像@evgenifotia所说的那样,替换每个调用以查看是否泄漏内存。另一种解决方案是,尽可能将代码封装并转化为功能性,这样就会有尽可能少的意外泄漏。 - ColdHands
显示剩余3条评论
3个回答

0

就像其他人在评论中提到的,从代码中看起来很难找到明显的原因。

你应该查看的一件事情是可能看一下这个函数在你的应用程序中被调用了多少次。你对User.findOne的调用。它是否会被限制,例如由于连接池大小的限制而排队或阻塞请求。

如果有更多的函数调用比可用资源/连接更多,你将开始看到内存使用量逐渐增加。

我不是说这是原因,但这绝对是我开始寻找问题的地方。


0
这里可能存在问题的是“game”对象和创建的用户对象。所以取决于它们去哪里(两者都被传递到sendMessageToSocketForComlitedGame中),它们可能会被排队或保留下来。我担心的是闭包泄漏,可能是某个数组或队列使它们保持活动状态,或者可能是一个定时器循环。您是否尝试将“sendMessageToSocketForComlitedGame”函数设置为无操作以查看内存问题是否消失?
另一个想法-这是在用户会话或游戏正在进行时的内存状态数据吗?是否有其他长期存在的数据结构在事件发生时被删除?因为这里看起来不像是永久泄漏,而是将数据保存一段时间。
由于您已经为此苦恼了几天,我相信您已经阅读了各种关于JavaScript泄漏的文章,但我发现这篇文章很好。我会查看此功能的上方和下方,看看是否有任何东西保留对已实例化闭包或对象的引用。
祝好运! -Darrin

0

我并没有优化或者针对代码中可能导致内存泄漏的漏洞进行定位,但是这里有一些提示可以帮助你避免这样的泄漏。

全局变量: 由于JavaScript中的全局变量是由根节点(window或global this)引用的,它们在应用程序的整个生命周期中都不会被垃圾回收,并且只要应用程序在运行,它们就会占用内存。这也适用于由全局变量引用的任何对象及其所有子对象。具有从根引用的大型对象图可能会导致内存泄漏。

多重引用: 当同一个对象被多个对象引用时,如果其中一个引用被悬空,可能会导致内存泄漏。

闭包: JavaScript闭包具有记忆其周围上下文的很酷的功能。当闭包持有堆中大型对象的引用时,只要闭包在使用,它就会将对象保留在内存中。这意味着您很容易陷入闭包持有此类引用并被错误使用导致内存泄漏的情况。

计时器和事件: 在回调函数中保留重型对象引用而没有进行适当处理的情况下,使用setTimeout、setInterval、Observers和事件监听器可能会导致内存泄漏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接