【问题标题】:Reflect.Emit Dynamic Type Memory BlowupReflect.Emit 动态类型内存爆炸
【发布时间】:2011-01-31 00:16:37
【问题描述】:

使用 C# 3.5 我正在尝试使用反射发射在运行时生成动态类型。我使用来自 Microsoft 的 Dynamic Query Library 示例来创建类生成器。一切正常,我的问题是 100 种生成的类型使内存使用量膨胀了大约 25MB。这是一个完全不可接受的内存配置文件,因为最终我希望支持在内存中生成数十万种类型。

内存分析显示内存显然被各种 System.Reflection.Emit 类型和方法占用,尽管我不知道为什么。我还没有发现其他人谈论这个问题,所以我希望这个社区中的某个人知道我做错了什么,或者这是否是预期的行为。

下面的人为示例:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace SmallRelfectExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int typeCount = 100;
            int propCount = 100;
            Random rand = new Random();
            Type dynType = null;
            SlimClassFactory scf = new SlimClassFactory();
            for (int i = 0; i < typeCount; i++)
            {
                List<DynamicProperty> dpl = new List<DynamicProperty>(propCount);
                for (int j = 0; j < propCount; j++)
                {
                    dpl.Add(new DynamicProperty("Key" + rand.Next().ToString(), typeof(String)));
                }
                dynType = scf.CreateDynamicClass(dpl.ToArray(), i);
                //Optionally do something with the type here
            }
            Console.WriteLine("SmallRelfectExample: {0} Types generated.", typeCount);
            Console.ReadLine();
        }
    }
    public class SlimClassFactory
    {
        private readonly ModuleBuilder module;
        public SlimClassFactory()
        {
            AssemblyName name = new AssemblyName("DynamicClasses");
            AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
            module = assembly.DefineDynamicModule("Module");

        }
        public Type CreateDynamicClass(DynamicProperty[] properties, int Id)
        {
            string typeName = "DynamicClass" + Id.ToString();
            TypeBuilder tb = module.DefineType(typeName, TypeAttributes.Class |
                TypeAttributes.Public, typeof(DynamicClass));
            FieldInfo[] fields = GenerateProperties(tb, properties);
            GenerateEquals(tb, fields);
            GenerateGetHashCode(tb, fields);
            Type result = tb.CreateType();
            return result;
        }
        static FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties)
        {
            FieldInfo[] fields = new FieldBuilder[properties.Length];
            for (int i = 0; i < properties.Length; i++)
            {
                DynamicProperty dp = properties[i];
                FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private);
                PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null);
                MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    dp.Type, Type.EmptyTypes);
                ILGenerator genGet = mbGet.GetILGenerator();
                genGet.Emit(OpCodes.Ldarg_0);
                genGet.Emit(OpCodes.Ldfld, fb);
                genGet.Emit(OpCodes.Ret);
                MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    null, new Type[] { dp.Type });
                ILGenerator genSet = mbSet.GetILGenerator();
                genSet.Emit(OpCodes.Ldarg_0);
                genSet.Emit(OpCodes.Ldarg_1);
                genSet.Emit(OpCodes.Stfld, fb);
                genSet.Emit(OpCodes.Ret);
                pb.SetGetMethod(mbGet);
                pb.SetSetMethod(mbSet);
                fields[i] = fb;
            }
            return fields;
        }
        static void GenerateEquals(TypeBuilder tb, FieldInfo[] fields)
        {
            MethodBuilder mb = tb.DefineMethod("Equals",
                MethodAttributes.Public | MethodAttributes.ReuseSlot |
                MethodAttributes.Virtual | MethodAttributes.HideBySig,
                typeof(bool), new Type[] { typeof(object) });
            ILGenerator gen = mb.GetILGenerator();
            LocalBuilder other = gen.DeclareLocal(tb);
            Label next = gen.DefineLabel();
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Isinst, tb);
            gen.Emit(OpCodes.Stloc, other);
            gen.Emit(OpCodes.Ldloc, other);
            gen.Emit(OpCodes.Brtrue_S, next);
            gen.Emit(OpCodes.Ldc_I4_0);
            gen.Emit(OpCodes.Ret);
            gen.MarkLabel(next);
            foreach (FieldInfo field in fields)
            {
                Type ft = field.FieldType;
                Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);
                next = gen.DefineLabel();
                gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldfld, field);
                gen.Emit(OpCodes.Ldloc, other);
                gen.Emit(OpCodes.Ldfld, field);
                gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null);
                gen.Emit(OpCodes.Brtrue_S, next);
                gen.Emit(OpCodes.Ldc_I4_0);
                gen.Emit(OpCodes.Ret);
                gen.MarkLabel(next);
            }
            gen.Emit(OpCodes.Ldc_I4_1);
            gen.Emit(OpCodes.Ret);
        }
        static void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields)
        {
            MethodBuilder mb = tb.DefineMethod("GetHashCode",
                MethodAttributes.Public | MethodAttributes.ReuseSlot |
                MethodAttributes.Virtual | MethodAttributes.HideBySig,
                typeof(int), Type.EmptyTypes);
            ILGenerator gen = mb.GetILGenerator();
            gen.Emit(OpCodes.Ldc_I4_0);
            foreach (FieldInfo field in fields)
            {
                Type ft = field.FieldType;
                Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);
                gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldfld, field);
                gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null);
                gen.Emit(OpCodes.Xor);
            }
            gen.Emit(OpCodes.Ret);
        }
    }
    public abstract class DynamicClass
    {
        public override string ToString()
        {
            PropertyInfo[] props = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            StringBuilder sb = new StringBuilder();
            sb.Append("{");
            for (int i = 0; i < props.Length; i++)
            {
                if (i > 0) sb.Append(", ");
                sb.Append(props[i].Name);
                sb.Append("=");
                sb.Append(props[i].GetValue(this, null));
            }
            sb.Append("}");
            return sb.ToString();
        }
    }
    public class DynamicProperty
    {
        private readonly string name;
        private readonly Type type;

        public DynamicProperty(string name, Type type)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (type == null) throw new ArgumentNullException("type");
            this.name = name;
            this.type = type;
        }

        public string Name
        {
            get { return name; }
        }

        public Type Type
        {
            get { return type; }
        }
    }
}

【问题讨论】:

  • 仅供参考,这在 .NET 4.0 中实际上有点糟糕。
  • 使用 CCI 或 Mono.Cecil 可能会有更好的性能。
  • @leppie 感谢 CCI 的建议。我正在考虑使用它。不得不以我的方式使用反射来解决问题,我感到很不舒服。
  • @leppie FYI CCI 目前不支持发射到动态程序集中的能力。我想我可以改为持久化、加载和变异,但这对我工作的环境有一些负面影响。
  • 我认为内部动态程序集也是如此。但这也是我的担忧。

标签: c# reflection dynamic reflection.emit


【解决方案1】:

不幸的是,ModuleBuilder 中有一个静态字段占用了内存,并且永远不会被 GC 处理。我不记得现在是哪个字段以及它包含什么,但这可以从 WinDbg 的 SOS 中看到。

好消息是 .NET 4 支持支持 GC 的动态程序集 :)

【讨论】:

  • @leppie 事实证明,程序集中的每个动态类型都保留了对 ModuleBuilder 和后续 TypeBuilder 的引用,用于解决类型名称冲突。如果您确保没有名称冲突,您可以清除 TypeBuilder 列表并释放所有内存,而不会产生任何负面影响。 (至少到目前为止)
  • @Firestrand:感谢您提供的信息 :)
  • @leppie 如何在 ModuleBuilder 私有变量 m__TypeBuilderList 上调用“清除”?
【解决方案2】:

这似乎是 System.Reflection.Emit 中的实际内存泄漏。 下面的新解决方案我能够通过使用反射和手动处置过程摆脱大部分内存。我使用扩展方法在某些类型上添加了 Dispose 方法。这不会清理所有内容,但代码显示了如何执行此操作。我正在转向一种不同的方式来获得我需要的结果。代码在这里是为那些对如何做感兴趣的人准备的。

在原始示例中,您将在生成类型后在 TypeBuilder 实例上调用 tb.Dispose()。扩展方法如下,请记住这不会清理所有内容,但会释放大部分内存。此代码也未针对速度进行优化。有一些方法可以加快反射速度,这只是一个例子。 使用风险自负。

  public static void Dispose(this TypeBuilder tb)
        {
            if (tb == null)
                return;
            Type tbType = typeof(TypeBuilder);
            FieldInfo tbMbList = tbType.GetField("m_listMethods", BindingFlags.Instance | BindingFlags.NonPublic); //List<MethodBuilder>
            FieldInfo tbDecType = tbType.GetField("m_DeclaringType", BindingFlags.Instance | BindingFlags.NonPublic);//TypeBuilder
            FieldInfo tbGenType = tbType.GetField("m_genTypeDef", BindingFlags.Instance | BindingFlags.NonPublic);//TypeBuilder
            FieldInfo tbDeclMeth = tbType.GetField("m_declMeth", BindingFlags.Instance | BindingFlags.NonPublic);//MethodBuilder
            FieldInfo tbMbCurMeth = tbType.GetField("m_currentMethod", BindingFlags.Instance | BindingFlags.NonPublic);//MethodBuilder
            FieldInfo tbMod = tbType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic);//ModuleBuilder
            FieldInfo tbGenTypeParArr = tbType.GetField("m_inst", BindingFlags.Instance | BindingFlags.NonPublic); //GenericTypeParameterBuilder[] 

            TypeBuilder tempDecType = tbDecType.GetValue(tb) as TypeBuilder;
            tempDecType.Dispose();
            tbDecType.SetValue(tb, null);
            tempDecType = tbGenType.GetValue(tb) as TypeBuilder;
            tempDecType.Dispose();
            tbDecType.SetValue(tb, null);

            MethodBuilder tempMeth = tbDeclMeth.GetValue(tb) as MethodBuilder;
            tempMeth.Dispose();
            tbDeclMeth.SetValue(tb,null);
            tempMeth = tbMbCurMeth.GetValue(tb) as MethodBuilder;
            tempMeth.Dispose();
            tbMbCurMeth.SetValue(tb, null);

            ArrayList mbList = tbMbList.GetValue(tb) as ArrayList;
            for (int i = 0; i < mbList.Count; i++)
            {
                tempMeth = mbList[i] as MethodBuilder;
                tempMeth.Dispose();
                mbList[i] = null;
            }
            tbMbList.SetValue(tb, null);

            ModuleBuilder tempMod = tbMod.GetValue(tb) as ModuleBuilder;
            tempMod.Dispose();
            tbMod.SetValue(tb, null);

            tbGenTypeParArr.SetValue(tb, null);
        }
        public static void Dispose(this MethodBuilder mb)
        {
            if (mb == null)
                return;
            Type mbType = typeof(MethodBuilder);
            FieldInfo mbILGen = mbType.GetField("m_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic);
            //FieldInfo mbIAttr = mbType.GetField("m_iAttributes", BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo mbMod = mbType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic); //ModuleBuilder 
            FieldInfo mbContType = mbType.GetField("m_containingType", BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo mbLocSigHelp = mbType.GetField("m_localSignature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper
            FieldInfo mbSigHelp = mbType.GetField("m_signature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper

            ILGenerator tempIlGen = mbILGen.GetValue(mb) as ILGenerator;
            tempIlGen.Dispose();
            SignatureHelper tempmbSigHelp = mbLocSigHelp.GetValue(mb) as SignatureHelper;
            tempmbSigHelp.Dispose();
            tempmbSigHelp = mbSigHelp.GetValue(mb) as SignatureHelper;
            tempmbSigHelp.Dispose();

            ModuleBuilder tempMod = mbMod.GetValue(mb) as ModuleBuilder;
            tempMod.Dispose();
            mbMod.SetValue(mb, null);

            mbILGen.SetValue(mb, null);
            mbContType.SetValue(mb, null);
            mbLocSigHelp.SetValue(mb, null);
            mbSigHelp.SetValue(mb, null);
            mbMod.SetValue(mb, null);
        }
        public static void Dispose(this SignatureHelper sh)
        {
            if (sh == null)
                return;
            Type shType = typeof(SignatureHelper);
            FieldInfo shModule = shType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic);
            //FieldInfo shSig = shType.GetField("m_signature", BindingFlags.Instance | BindingFlags.NonPublic);
            shModule.SetValue(sh, null);
            //shSig.SetValue(sh, null);
        }
        public static void Dispose(this ILGenerator ilGen)
        {
            if (ilGen == null)
                return;
            Type ilGenType = typeof(ILGenerator);
            FieldInfo ilSigHelp = ilGenType.GetField("m_localSignature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper
            SignatureHelper sigTemp = ilSigHelp.GetValue(ilGen) as SignatureHelper;
            sigTemp.Dispose();
            ilSigHelp.SetValue(ilGen, null);
        }
        public static void Dispose(this ModuleBuilder modBuild)
        {
            if (modBuild == null)
                return;
            Type modBuildType = typeof(ModuleBuilder);
            FieldInfo modBuildModData = modBuildType.GetField("m__moduleData", BindingFlags.Instance | BindingFlags.NonPublic |BindingFlags.FlattenHierarchy );
            FieldInfo modTypeBuildList = modBuildType.GetField("m__TypeBuilderList", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);

            ArrayList modTypeList = modTypeBuildList.GetValue(modBuild) as ArrayList;
            if(modTypeList != null)
            {
                for (int i = 0; i < modTypeList.Count; i++)
                {
                    TypeBuilder tb = modTypeList[i] as TypeBuilder;
                    tb.Dispose();
                    modTypeList = null;
                }
                modTypeBuildList.SetValue(modBuild, null);
            }
            modBuildModData.SetValue(modBuild, null);
        }

编辑 找到了实际原因:似乎在动态程序集中创建的每个类型都包含对ModuleBuilder(在Type.Module 中)的引用,而后者又保留了TypeBuilder 对象的列表。每次添加类型时都会扫描此列表以检查名称冲突。如果您将 HashSet 保留在类型生成例程之外,以确保不会出现任何名称冲突,您可以在生成 Type 后对 ModuleBuilder 私有变量 m__TypeBuilderList 调用 Clear ,而不会产生任何负面影响(到目前为止)

【讨论】:

  • 如何在 ModuleBuilder 私有变量 m__TypeBuilderList 上调用“清除”?
  • 在 .NET 4.0 中,它更改为 Dictionary&lt;string, Type&gt;(其中 Type 实际上是 TypeBuilder),名称为 m_TypeBuilderDict
【解决方案3】:

嗯,我注意到的第一件事是您正在创建一个新工厂,因此每次迭代都是新的AssemblyBuilder。能否重用工厂(在同一个动态程序集中创建多个类型)?

【讨论】:

  • 很好,实际上没有区别。稍后我会在这里更新代码。
【解决方案4】:

无论您现在看到的实际问题是什么,我都强烈建议您反对您当前的方法。 Reflection.Emit 并非旨在支持创建数十万种类型(例如,请参阅this connect issue,尽管该特定问题可能仅在您将它们全部放入单个动态程序集中时才适用)。为什么需要创建这么多类型?

【讨论】:

  • 感谢您的链接。我将把这个问题放在连接上,因为我一直无法找到解决方案。有这么多动态类型的原因是我需要支持许多客户端连接到应用程序并在运行时创建新类型的能力,然后我需要能够持久化并从数据库中检索这些类型。不幸的是,对于这个内存问题,我将不得不寻找其他解决方案来解决这个问题。
  • @Firestrand - 我可以理解需要在运行时动态创建一些类型,但是十万种类型很多。例如,mscorlib 总共只有几千个。
  • 真正的几十万是上限,但在内存泄漏明显的测试期间,即使是一千也将令人望而却步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-12
  • 1970-01-01
  • 1970-01-01
  • 2020-04-12
  • 1970-01-01
  • 2010-12-18
  • 2015-06-11
相关资源
最近更新 更多