#==============================================================================
# ■ RGSS3 检测内存泄漏V3 by SaiCateDoan
#------------------------------------------------------------------------------
# 创意来源:taroxd
#
# 使用方法:
# 1.插入到Main脚本的上面,所有其他脚本的下面
# 2.正常玩游戏一段时间,进行几次战斗
# 3.在地图界面按下Alt,建议多按几次
# 4.重复2-3数次,或重复到游戏卡顿(当然你也可以偷懒)
# 5.检查未释放对象数量是否异常增长
# 具体来说,如果有数百个未释放对象,那其实是正常的(来源于脚本的Cache)
# 但如果未释放对象数量上千,并且还在不断增加,那就很可能发生了内存泄漏
# 6.在控制台中检查调用栈,寻找其中重复出现的关键词
# 7.将关键词写到下面的配置中,然后重复以上步骤(可选)
# 如果想追踪内存泄漏发生的时间,就修改TRIGGER_KEYWORD,然后时刻检查控制台
# 如果想追踪内存泄漏的速度,就修改STAT_KEYWORDS,检查不同脚本泄漏的数量
# 8.在脚本编辑器界面按下Ctrl+Shift+F,全局搜索关键词,找到发生内存泄漏的脚本
# 9.修复它!(如果看不懂,就去问AI吧)
#------------------------------------------------------------------------------
# 内存泄露检测系统配置(可修改)
TRIGGER_KEYWORD = '我是关键词' # 触发详细调用栈打印的关键词
STAT_KEYWORDS = [:我是关键词, :我也是关键词] # 需要统计的关键词列表
# 扩展Viewport类添加内存释放标记功能
class Viewport
def_before(:dispose) { @__disposed__ = true }
def disposed?; @__disposed__; end
end
need_dispose = [Bitmap, Sprite, Window, Plane, Tilemap, Viewport] # 需要监控的对象类型
$caller_stack_db = {} # 存储对象创建信息的全局数据库
not_disposed = [] # 记录当前未释放的对象列表
# 为所有监控类添加创建追踪功能
need_dispose.each do |klass|
klass.class_eval do
# 追踪initialize操作创建的对象
def_after(:initialize) do |*|
record_creation_info(self)
end
alias_method :original_clone, :clone
alias_method :original_dup, :dup
# 追踪clone操作创建的对象
def clone
obj = original_clone
record_creation_info(obj)
obj
end
# 追踪dup操作创建的对象
def dup
obj = original_dup
record_creation_info(obj)
obj
end
end
end
# 记录对象创建时的调用栈和相关信息
def record_creation_info(obj)
stack = Kernel.caller
contains_keyword = stack.any? { |line| line.include?(TRIGGER_KEYWORD) }
# 触发关键词命中时打印完整调用栈
if contains_keyword
puts "[#{TRIGGER_KEYWORD}触发] 对象类型: #{obj.class}"
puts "完整调用栈:"
stack.each { |line| puts "> #{line}" }
puts "-" * 20
end
# 保存对象创建信息到全局数据库
$caller_stack_db[obj] = {
:stack => stack,
:timestamp => Time.now,
:location => stack.first.to_s
}
end
# 在场景切换时捕获未释放对象
Scene_Base.class_eval do
def_after :terminate do
current_not_disposed = []
need_dispose.each do |klass|
ObjectSpace.each_object(klass) do |obj|
current_not_disposed << obj unless obj.disposed?
end
end
not_disposed.replace(current_not_disposed)
end
# ALT键触发内存分析报告
def_after :update do
return unless Input.trigger?(:ALT)
# 1. 随机对象分析:显示单个对象的详细信息
target_obj = not_disposed.sample
puts target_obj
puts "对象类型: #{target_obj.class}"
if target_obj && $caller_stack_db[target_obj]
puts "创建方式: #{ $caller_stack_db[target_obj][:stack].any? { |line| line.include?('clone') } ? 'clone' :
$caller_stack_db[target_obj][:stack].any? { |line| line.include?('dup') } ? 'dup' : 'new' }"
puts "创建时间: #{$caller_stack_db[target_obj][:timestamp]}"
puts "创建位置: #{$caller_stack_db[target_obj][:location]}"
puts "调用栈:"
$caller_stack_db[target_obj][:stack].each { |line| puts "> #{line}" }
end
# 2. 关键词统计:显示特定关键词的出现频率
keyword_stats = Hash.new(0)
not_disposed.each do |obj|
next unless $caller_stack_db[obj]
found_keywords = {}
$caller_stack_db[obj][:stack].each do |line|
STAT_KEYWORDS.each do |kw|
if !found_keywords[kw] && line.include?(kw.to_s)
found_keywords[kw] = true
keyword_stats[kw] += 1
end
end
break if found_keywords.size == STAT_KEYWORDS.size
end
end
keyword_stats.each { |kw, count| puts "包含关键词 #{kw} 的对象数量: #{count}" }
# 3. 全局统计:对象类型和创建方式的分布情况
type_stats = Hash.new(0)
create_method_stats = Hash.new(0)
not_disposed.each do |obj|
next unless $caller_stack_db[obj]
type_stats[obj.class] += 1
stack = $caller_stack_db[obj][:stack]
create_method_stats[stack.any? { |line| line =~ /`clone'/ || line.include?('clone') } ? 'clone' :
stack.any? { |line| line =~ /`dup'/ || line.include?('dup') } ? 'dup' : 'new'] += 1
end
puts "\n未释放对象类型统计:"
type_stats.each { |klass, count| puts "#{klass.name}: #{count}" }
puts "\n对象创建方式统计:"
create_method_stats.each { |method, count| puts "#{method}: #{count}" }
end
end