Reflect.Emit Dynamic Type Memory Blowup
使用C#3.5我试图在运行时使用reflection发射生成动态类型。 我使用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 dpl = new List(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 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; } } } }
不幸的是,ModuleBuilder中有一个静态字段保存在内存中,并且永远不会得到GC。 我不记得现在哪个领域及其包含的内容,但这可以在WinDbg的SOS中看到。
好消息是.NET 4支持GC-able动态组件:)
这似乎是System.Reflection.Emit中的实际内存泄漏。 以下新解决方案我能够通过使用reflection和手动处理过程来消除大部分内存。 我使用扩展方法在某些类型上添加Dispose方法。 这不会清除所有内容,但代码会显示如何执行此操作。 我正在以不同的方式获得我需要的结果。 这里的代码适合那些对如何操作感兴趣的人。
在原始示例中,您将在生成类型后在TypeBuilder实例上调用tb.Dispose()
。 扩展方法如下,请记住, 这并不能清除所有内容,但确实释放了大部分内存。 此代码也未针对速度进行优化。 有很多方法可以加快使用的reflection速度,这只是一个例子。 使用风险由您自己承担。
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 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); }
编辑找到了实际原因:似乎在动态程序集中创建的每个Type都包含对Type.Module
的引用(在Type.Module
),而Type.Module
又保留了TypeBuilder
对象的列表。 每次添加类型时都会扫描此列表以检查名称冲突。 如果你将HashSet
保留在Type生成例程中以确保没有任何名称冲突,那么在生成Type
之后可以在m__TypeBuilderList
私有变量m__TypeBuilderList
上调用Clear而不会产生任何负面影响(So Far)
好吧,我要注意的第一件事是你每次迭代都要创建一个新工厂,因此也就是新的AssemblyBuilder
。 您可以重新使用工厂(在同一动态assembly中创建多种类型)吗?
无论您现在看到的实际问题如何,我强烈建议您不要使用当前的方法。 Reflection.Emit并非旨在支持创建数十万种类型(例如,请参阅此连接问题 ,尽管该特定问题可能仅适用于您将它们全部放入单个动态程序集中)。 为什么需要创建那么多类型?
上述就是C#学习教程:Reflect.Emit Dynamic Type Memory Blowup分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1040764.html