设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 20|回复: 1
打印 上一主题 下一主题

[原创发布] 内存泄漏检测脚本V3

[复制链接]

Lv1.梦旅人

梦石
0
星屑
90
在线时间
4 小时
注册时间
2025-6-8
帖子
2
跳转到指定楼层
1
发表于 4 小时前 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
创意来源:https://rpg.blue/thread-369217-1-1.html
非常感谢原作者给我提供了思路,但是原本的脚本存在几个问题:数据收集不全,统计方式错误,实际用起来效果并不理想
所以我请DeepSeek帮我拓展了一下脚本,修复了原本脚本的bug基础上,多了一些花里胡哨但其实没太大用的统计功能
不要问我为什么是V3

如何判断发生了内存泄漏:
在游戏运行一段时间后,明明没干什么特别的事情,游戏突然变得卡顿,而且越来越卡,最后闪退,就很可能是内存泄漏
如果同时还导致了其他程序的崩溃,那就基本可以确定了

为什么会发生内存泄漏:
Rpgmaker中一部分资源是需要使用dispose手动清理的,如果没人清理,即使使用资源的变量已经销毁,也不会释放内存

如何修复内存泄漏:
我唯一能给出的建议就是拿着内存泄漏的脚本去问AI

如何找到发生内存泄漏的脚本:
RUBY 代码复制
  1. #==============================================================================
  2. # ■ RGSS3 检测内存泄漏V3 by SaiCateDoan
  3. #------------------------------------------------------------------------------
  4. # 创意来源:taroxd
  5. #
  6. # 使用方法:
  7. # 1.插入到Main脚本的上面,所有其他脚本的下面
  8. # 2.正常玩游戏一段时间,进行几次战斗
  9. # 3.在地图界面按下Alt,建议多按几次
  10. # 4.重复2-3数次,或重复到游戏卡顿(当然你也可以偷懒)
  11. # 5.检查未释放对象数量是否异常增长
  12. #   具体来说,如果有数百个未释放对象,那其实是正常的(来源于脚本的Cache)
  13. #   但如果未释放对象数量上千,并且还在不断增加,那就很可能发生了内存泄漏
  14. # 6.在控制台中检查调用栈,寻找其中重复出现的关键词
  15. # 7.将关键词写到下面的配置中,然后重复以上步骤(可选)
  16. #   如果想追踪内存泄漏发生的时间,就修改TRIGGER_KEYWORD,然后时刻检查控制台
  17. #   如果想追踪内存泄漏的速度,就修改STAT_KEYWORDS,检查不同脚本泄漏的数量
  18. # 8.在脚本编辑器界面按下Ctrl+Shift+F,全局搜索关键词,找到发生内存泄漏的脚本
  19. # 9.修复它!(如果看不懂,就去问AI吧)
  20. #------------------------------------------------------------------------------
  21.  
  22. # 内存泄露检测系统配置(可修改)
  23. TRIGGER_KEYWORD = '我是关键词'  # 触发详细调用栈打印的关键词
  24. STAT_KEYWORDS = [:我是关键词, :我也是关键词]  # 需要统计的关键词列表
  25.  
  26. # 扩展Viewport类添加内存释放标记功能
  27. class Viewport
  28.   def_before(:dispose) { @__disposed__ = true }
  29.   def disposed?; @__disposed__; end
  30. end
  31.  
  32. need_dispose = [Bitmap, Sprite, Window, Plane, Tilemap, Viewport]  # 需要监控的对象类型
  33. $caller_stack_db = {}        # 存储对象创建信息的全局数据库
  34. not_disposed = []            # 记录当前未释放的对象列表
  35.  
  36. # 为所有监控类添加创建追踪功能
  37. need_dispose.each do |klass|
  38.   klass.class_eval do
  39.     # 追踪initialize操作创建的对象
  40.     def_after(:initialize) do |*|
  41.       record_creation_info(self)
  42.     end
  43.  
  44.     alias_method :original_clone, :clone
  45.     alias_method :original_dup, :dup
  46.  
  47.     # 追踪clone操作创建的对象
  48.     def clone
  49.       obj = original_clone
  50.       record_creation_info(obj)
  51.       obj
  52.     end
  53.  
  54.     # 追踪dup操作创建的对象
  55.     def dup
  56.       obj = original_dup
  57.       record_creation_info(obj)
  58.       obj
  59.     end
  60.   end
  61. end
  62.  
  63. # 记录对象创建时的调用栈和相关信息
  64. def record_creation_info(obj)
  65.   stack = Kernel.caller
  66.   contains_keyword = stack.any? { |line| line.include?(TRIGGER_KEYWORD) }
  67.  
  68.   # 触发关键词命中时打印完整调用栈
  69.   if contains_keyword
  70.     puts "[#{TRIGGER_KEYWORD}触发] 对象类型: #{obj.class}"
  71.     puts "完整调用栈:"
  72.     stack.each { |line| puts "> #{line}" }
  73.     puts "-" * 20
  74.   end
  75.  
  76.   # 保存对象创建信息到全局数据库
  77.   $caller_stack_db[obj] = {
  78.     :stack => stack,
  79.     :timestamp => Time.now,
  80.     :location => stack.first.to_s
  81.   }
  82. end
  83.  
  84. # 在场景切换时捕获未释放对象
  85. Scene_Base.class_eval do
  86.   def_after :terminate do
  87.     current_not_disposed = []
  88.     need_dispose.each do |klass|
  89.       ObjectSpace.each_object(klass) do |obj|
  90.         current_not_disposed << obj unless obj.disposed?
  91.       end
  92.     end
  93.     not_disposed.replace(current_not_disposed)
  94.   end
  95.  
  96.   # ALT键触发内存分析报告
  97.   def_after :update do
  98.     return unless Input.trigger?(:ALT)
  99.  
  100.     # 1. 随机对象分析:显示单个对象的详细信息
  101.     target_obj = not_disposed.sample
  102.     puts target_obj
  103.     puts "对象类型: #{target_obj.class}"
  104.     if target_obj && $caller_stack_db[target_obj]
  105.       puts "创建方式: #{ $caller_stack_db[target_obj][:stack].any? { |line| line.include?('clone') } ? 'clone' :
  106.               $caller_stack_db[target_obj][:stack].any? { |line| line.include?('dup') } ? 'dup' : 'new' }"
  107.       puts "创建时间: #{$caller_stack_db[target_obj][:timestamp]}"
  108.       puts "创建位置: #{$caller_stack_db[target_obj][:location]}"
  109.       puts "调用栈:"
  110.       $caller_stack_db[target_obj][:stack].each { |line| puts "> #{line}" }
  111.     end
  112.  
  113.     # 2. 关键词统计:显示特定关键词的出现频率
  114.     keyword_stats = Hash.new(0)
  115.     not_disposed.each do |obj|
  116.       next unless $caller_stack_db[obj]
  117.       found_keywords = {}
  118.       $caller_stack_db[obj][:stack].each do |line|
  119.         STAT_KEYWORDS.each do |kw|
  120.           if !found_keywords[kw] && line.include?(kw.to_s)
  121.             found_keywords[kw] = true
  122.             keyword_stats[kw] += 1
  123.           end
  124.         end
  125.         break if found_keywords.size == STAT_KEYWORDS.size
  126.       end
  127.     end
  128.  
  129.     keyword_stats.each { |kw, count| puts "包含关键词 #{kw} 的对象数量: #{count}" }
  130.  
  131.     # 3. 全局统计:对象类型和创建方式的分布情况
  132.     type_stats = Hash.new(0)
  133.     create_method_stats = Hash.new(0)
  134.     not_disposed.each do |obj|
  135.       next unless $caller_stack_db[obj]
  136.       type_stats[obj.class] += 1
  137.       stack = $caller_stack_db[obj][:stack]
  138.       create_method_stats[stack.any? { |line| line =~ /`clone'/ || line.include?('clone') } ? 'clone' :
  139.                          stack.any? { |line| line =~ /`dup'/ || line.include?('dup') } ? 'dup' : 'new'] += 1
  140.     end
  141.  
  142.     puts "\n未释放对象类型统计:"
  143.     type_stats.each { |klass, count| puts "#{klass.name}: #{count}" }
  144.  
  145.     puts "\n对象创建方式统计:"
  146.     create_method_stats.each { |method, count| puts "#{method}: #{count}" }
  147.   end
  148. end

评分

参与人数 1星屑 +30 +1 收起 理由
寂静的夜里 + 30 + 1 塞糖

查看全部评分

Lv1.梦旅人

梦石
0
星屑
90
在线时间
4 小时
注册时间
2025-6-8
帖子
2
2
 楼主| 发表于 4 小时前 | 只看该作者
另外,由于内存泄漏发生在joiplay上时后果通常更加严重,而且joiplay缺少获取控制台输出的手段
所以就有了下面这个便宜脚本,方便在joiplay上调试游戏
  1. #==============================================================================
  2. # ■ RGSS3 控制台重定向到文件 by SaiCateDoan
  3. #------------------------------------------------------------------------------
  4. # 日志文件路径(默认在游戏根目录)
  5. log_file = "debug_log.txt"

  6. # 备份原始标准输出
  7. $original_stdout = $stdout.dup if $original_stdout.nil?

  8. # 重定向输出到文件(追加模式)
  9. $stdout.reopen(log_file, "a")
  10. $stdout.sync = true  # 实时写入

  11. # 添加时间戳和分隔符
  12. puts "\n\n===== Log Started: #{Time.now} ====="

  13. # 确保游戏退出时关闭文件流
  14. at_exit do
  15.   puts "===== Log Ended: #{Time.now} ====="
  16.   $stdout.close
  17.   $stdout = $original_stdout  # 恢复原始输出
  18. end
复制代码
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2025-7-20 15:12

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表