Montón de objetos grandes en sistemas WindowsThe large object heap on Windows systems

El recolector de elementos no utilizados de .NET divide los objetos en pequeños y grandes.The .NET Garbage Collector (GC) divides objects up into small and large objects. Cuando un objeto es grande, algunos de sus atributos son más importantes que si fuera pequeño.When an object is large, some of its attributes become more significant than if the object is small. Por ejemplo, su compactación (es decir, copiarlo en memoria en cualquier parte del montón) puede resultar cara.For instance, compacting it -- that is, copying it in memory elsewhere on the heap -- can be expensive. Por este motivo, el recolector de elementos no utilizados de .NET coloca los objetos grandes en el montón de objetos grandes.Because of this, the .NET Garbage Collector places large objects on the large object heap (LOH). En este tema, veremos el montón de objetos grandes en profundidad.In this topic, we'll look at the large object heap in depth. Nos centraremos en qué caracteriza a un objeto como objeto grande, cómo se recolectan estos objetos grandes y qué tipo de implicaciones de rendimiento conllevan los objetos grandes.We'll discuss what qualifies an object as a large object, how these large objects are collected, and what kind of performance implications large objects impose.

Importante

En este tema se describe el montón de objetos grandes en .NET Framework y .NET Core cuando se ejecutan únicamente en sistemas Windows.This topic discusses the large object heap in the .NET Framework and .NET Core running on Windows systems only. No se aborda el montón de objetos grandes en implementaciones de .NET en otras plataformas.It does not cover the LOH running on .NET implementations on other platforms.

Cómo los objetos acaban en el montón de objetos grandes y cómo el recolector de elementos no utilizados los administraHow an object ends up on the large object heap and how GC handles them

Si un objeto tiene un tamaño mayor o igual que 85 000 bytes, se considera un objeto grande.If an object is greater than or equal to 85,000 bytes in size, it’s considered a large object. Este número venía determinado por el ajuste de rendimiento.This number was determined by performance tuning. Cuando una solicitud de asignación de objeto es de 85 000 o más bytes, el tiempo de ejecución la asigna al montón de objetos grandes.When an object allocation request is for 85,000 or more bytes, the runtime allocates it on the large object heap.

Para entender lo que esto significa, viene bien examinar algunos conceptos básicos relativos al recolector de elementos no utilizados de .NET.To understand what this means, it's useful to examine some fundamentals about the .NET GC.

El recolector de elementos no utilizados de .NET es un recolector generacional;The .NET Garbage Collector is a generational collector. es decir, tiene tres generaciones: generación 0, generación 1 y generación 2.It has three generations: generation 0, generation 1, and generation 2. El motivo para tener tres generaciones reside en que, en una aplicación bien ajustada, la mayoría de los objetos no pasa de la generación 0.The reason for having 3 generations is that, in a well-tuned app, most objects die in gen0. Por ejemplo, en una aplicación de servidor, las asignaciones asociadas a cada solicitud deben agotarse después de que la solicitud finalice.For example, in a server app, the allocations associated with each request should die after the request is finished. Las solicitudes de asignación al vuelo pasarán a la generación 1 y allí dejarán de estar activas.The in-flight allocation requests will make it into gen1 and die there. Básicamente, la generación 1 actúa de búfer entre las áreas de objetos jóvenes y las áreas de objetos de larga vida.Essentially, gen1 acts as a buffer between young object areas and long-lived object areas.

Los objetos pequeños siempre se asignan en la generación 0 y, según cuál sea su duración, pueden subir a la generación 1 o a la generación 2.Small objects are always allocated in generation 0 and, depending on their lifetime, may be promoted to generation 1 or generation2. Los objetos grandes siempre se asignan a la generación 2.Large objects are always allocated in generation 2.

Los objetos grandes pertenecen a la generación 2 porque se recolectan únicamente durante una recolección de generación 2.Large objects belong to generation 2 because they are collected only during a generation 2 collection. Cuando una generación se recolecta, también se recolectan todas sus generaciones más jóvenes.When a generation is collected, all its younger generation(s) are also collected. Por ejemplo, cuando se produce una recolección de elementos no utilizados de generación 1, se recolectan las generaciones 1 y 0,For example, when a generation 1 GC happens, both generation 1 and 0 are collected. mientras que, cuando se produce una de generación 2, se recolecta todo el montón.And when a generation 2 GC happens, the whole heap is collected. Este es el motivo por el que las recolecciones de elementos no utilizados de generación 2 también se conocen como recolecciones de elementos no utilizados completas.For this reason, a generation 2 GC is also called a full GC. En este artículo hablaremos de recolecciones de elementos no utilizados de generación 2 en lugar completas, si bien ambas son indistintas.This article refers to generation 2 GC instead of full GC, but the terms are interchangeable.

Las generaciones proporcionan una vista lógica del montón del recolector de elementos no utilizados.Generations provide a logical view of the GC heap. Físicamente, los objetos se encuentran en segmentos de montón administrado.Physically, objects live in managed heap segments. Un segmento de montón administrado es un fragmento de memoria que el recolector de elementos no utilizados reserva del sistema operativo, para lo cual llama a la función VirtualAlloc en nombre del código administrado.A managed heap segment is a chunk of memory that the GC reserves from the OS by calling the VirtualAlloc function on behalf of managed code. Cuando el CLR se carga, el recolector de elementos no utilizados asigna dos segmentos de montón iniciales: uno para objetos pequeños (montón de objetos pequeños) y otro para objetos grandes (montón de objetos grandes).When the CLR is loaded, the GC allocates two initial heap segments: one for small objects (the small object heap, or SOH), and one for large objects (the large object heap).

Tras ello, las solicitudes de asignación se cumplen colocando objetos administrados en cualquiera de estos dos segmentos de montón administrados.The allocation requests are then satisfied by putting managed objects on these managed heap segments. Si el objeto tiene un tamaño inferior a 85 000 bytes, se coloca en el segmento de montón de objetos pequeños y, si no, se coloca en el segmento de montón de objetos grandes.If the object is less than 85,000 bytes, it is put on the segment for the SOH; otherwise, it is put on an LOH segment. Los segmentos se confirman (en fragmentos menores) a medida que más y más objetos se asignan a ellos.Segments are committed (in smaller chunks) as more and more objects are allocated onto them. En el montón de objetos pequeños, aquellos objetos que sobrevivan a una recolección de elementos no utilizados se promueven a la siguiente generación.For the SOH, objects that survive a GC are promoted to the next generation. Los objetos que sobrevivan a una recolección de generación 0 pasarán a considerarse objetos de la generación 1, y así sucesivamente.Objects that survive a generation 0 collection are now considered generation 1 objects, and so on. En cambio, los objetos que sobrevivan a la generación más antigua se seguirán considerando pertenecientes a ella.However, objects that survive the oldest generation are still considered to be in the oldest generation. Es decir, los supervivientes de la generación 2 son objetos de la generación 2 y los supervivientes del montón de objeto grande serán objetos de montón de objeto grande (que se recolectan con la generación 2).In other words, survivors from generation 2 are generation 2 objects; and survivors from the LOH are LOH objects (which are collected with gen2).

El código de usuario solo puede realizar la asignación en la generación 0 (objetos pequeños) o el montón de objetos grandes (objetos grandes).User code can only allocate in generation 0 (small objects) or the LOH (large objects). El recolector de elementos no utilizados es el único que puede "asignar" objetos en la generación 1 (promoviendo los supervivientes de la generación 0) y en la generación 2 (promoviendo los supervivientes de las generaciones 1 y 2).Only the GC can “allocate” objects in generation 1 (by promoting survivors from generation 0) and generation 2 (by promoting survivors from generations 1 and 2).

Cuando se activa una recolección de elementos no utilizados, el recolector de elementos no utilizados realiza el seguimiento de los objetos activos y los compacta.When a garbage collection is triggered, the GC traces through the live objects and compacts them. Pero, dado que la compactación resulta cara, el recolector de elementos no utilizados barre el montón de objetos grandes; dicho de otro modo, confecciona una lista de los objetos inactivos que se puedan reutilizar más adelante para satisfacer las solicitudes de asignación de objetos grandes.But because compaction is expensive, the GC sweeps the LOH; it makes a free list out of dead objects that can be reused later to satisfy large object allocation requests. Los objetos inactivos adyacentes se convierten en un objeto libre.Adjacent dead objects are made into one free object.

.NET Core y .NET Framework (a partir de .NET Framework 4.5.1) incluyen la propiedad GCSettings.LargeObjectHeapCompactionMode, con la que los usuarios pueden especificar que el montón de objetos grandes se compacte durante la siguiente recolección de elementos no utilizados de bloqueo completo..NET Core and .NET Framework (starting with .NET Framework 4.5.1) include the GCSettings.LargeObjectHeapCompactionMode property that allows users to specify that the LOH should be compacted during the next full blocking GC. En un futuro próximo, cabrá la posibilidad de que .NET decida compactar el montón de objetos grandes automáticamente.And in the future, .NET may decide to compact the LOH automatically. Esto significa que, si asigna objetos grandes y quiere asegurarse de que no se mueven, deberá fijarlos.This means that, if you allocate large objects and want to make sure that they don’t move, you should still pin them.

En la figura 1 se ilustra un escenario en el que el recolector de elementos no utilizados crea la generación 1 después de la primera recolección de elementos no utilizados de generación 0, donde Obj1 y Obj3 están inactivos, y crea la generación 2 después de la primera recolección de elementos no utilizados de generación 1, donde Obj2 y Obj5 están inactivos.Figure 1 illustrates a scenario where the GC forms generation 1 after the first generation 0 GC where Obj1 and Obj3 are dead, and it forms generation 2 after the first generation 1 GC where Obj2 and Obj5 are dead. Cabe mencionar que tanto esta como las demás figuras se muestran a título meramente ilustrativo; contienen muy pocos objetos para mostrar de mejor forma lo que sucede en el montón.Note that this and the following figures are only for illustration purposes; they contain very few objects to better show what happens on the heap. En realidad, en una recolección de elementos no utilizados suele haber muchos más objetos.In reality, many more objects are typically involved in a GC.

Figura 1: recolecciones de elementos no utilizados de generación 0 y de generación 1
Figura 1: recolecciones de elementos no utilizados de generación 0 y de generación 1.Figure 1: A generation 0 and a generation 1 GC.

En la figura 2 se muestra que, después de una recolección de elementos no utilizados de generación 2 en la que se apreciaba que Obj1 y Obj2 estaban inactivos, el recolector de elementos no utilizados libera el espacio en memoria que solía estar ocupado por Obj1 y Obj2, que pasa a usarse para cumplir una solicitud de asignación de Obj4.Figure 2 shows that after a generation 2 GC which saw that Obj1 and Obj2 are dead, the GC forms contiguous free space out of memory that used to be occupied by Obj1 and Obj2, which then was used to satisfy an allocation request for Obj4. El espacio que hay después del último objeto, Obj3, hasta el final del segmento se puede usar también para cumplir solicitudes de asignación.The space after the last object, Obj3, to end of the segment can also be used to satisfy allocation requests.

Figura 2: Después de una recolección de elementos no utilizados de generación 2
Figura 2: Después de una recolección de elementos no utilizados de generación 2Figure 2: After a generation 2 GC

Si no existe suficiente espacio libre para las solicitudes de asignación de objeto grande, el recolector de elementos no utilizados intenta primero adquirir más segmentos del sistema operativo.If there isn't enough free space to accommodate the large object allocation requests, the GC first attempts to acquire more segments from the OS. Si esto no sirve, activa una recolección de elementos no utilizados de generación 2 con la esperanza de liberar algo de espacio.If that fails, it triggers a generation 2 GC in the hope of freeing up some space.

Durante una recolección de elementos no utilizados de generación 1 o de generación 2, el recolector de elementos no utilizados libera los segmentos que no tengan objetos activos para el sistema operativo (llamando a la función VirtualFree).During a generation 1 or generation 2 GC, the garbage collector releases segments that have no live objects on them back to the OS by calling the VirtualFree function. De este modo, se anula la confirmación del espacio tras el último objeto activo hasta el final del segmento (excepto en el segmento efímero de las generaciones 0 y 1, donde el recolector de elementos no utilizados mantiene confirmado algo de espacio, ya que es posible que la aplicación lo asigne inmediatamente).Space after the last live object to the end of the segment is decommitted (except on the ephemeral segment where gen0/gen1 live, where the garbage collector does keep some committed because your application will be allocating in it right away). Además, los espacios libres se quedan confirmados aunque se restablezcan, lo que significa que el sistema operativo no necesita escribir los datos que contienen al disco.And the free spaces remain committed though they are reset, meaning that the OS doesn’t need to write data in them back to disk.

Como el montón de objetos grandes solo se recopila durante las recolecciones de elementos no utilizados de generación 2, el segmento del montón de objetos grandes solo se puede liberar durante una recolección de este tipo.Since the LOH is only collected during generation 2 GCs, the LOH segment can only be freed during such a GC. En la figura 3 se ilustra un escenario en el que el recolector de elementos no utilizados libera un segmento (segmento 2) para el sistema operativo y anula la confirmación de más espacio en los segmentos restantes.Figure 3 illustrates a scenario where the garbage collector releases one segment (segment 2) back to the OS and decommits more space on the remaining segments. Si necesitara usar el espacio que no está confirmado al final del segmento para cumplir asignaciones de objetos grandes, confirmará la memoria de nuevo.If it needs to use the decommitted space at the end of the segment to satisfy large object allocation requests, it commits the memory again. Para obtener una explicación sobre cómo confirmar/anular confirmaciones, vea la documentación de VirtualAlloc.(For an explanation of commit/decommit, see the documentation for VirtualAlloc.

Figura 3: Montón de objetos grandes después de una recolección de elementos no utilizados de generación 2
Figura 3: Montón de objetos grandes después de una recolección de elementos no utilizados de generación 2Figure 3: The LOH after a generation 2 GC

¿Cuándo se recolecta un objeto grande?When is a large object collected?

Por lo general, una recolección de elementos no utilizados tiene lugar cuando se cumple una de las tres condiciones siguientes:In general, a GC occurs when one of the following 3 conditions happens:

  • La asignación supera el umbral de la generación 0 o de objeto grande.Allocation exceeds the generation 0 or large object threshold.

    El umbral es una propiedad de una generación.The threshold is a property of a generation. El umbral de una generación se establece cuando el recolector de elementos no utilizados asigna objetos a esa generación.A threshold for a generation is set when the garbage collector allocates objects into it. Cuando el umbral se supera, se activa una recolección de elementos no utilizados en dicha generación.When the threshold is exceeded, a GC is triggered on that generation. Cuando se asignan objetos pequeños o grandes, se consumen los umbrales de la generación 0 y del montón de objeto grande respectivamente.When you allocate small or large objects, you consume generation 0 and the LOH’s thresholds, respectively. Cuando el recolector de elementos no utilizados realiza la asignación en las generaciones 1 y 2, consume sus umbrales correspondientes.When the garbage collector allocates into generation 1 and 2, it consumes their thresholds. Estos umbrales se optimizan dinámicamente a medida que se ejecuta el programa.These thresholds are dynamically tuned as the program runs.

    Esto es lo habitual; la mayoría de las recolecciones de elementos no utilizados ocurren debido a las asignaciones del montón administrado.This is the typical case; most GCs happen because of allocations on the managed heap.

  • Se llama al método GC.Collect .The GC.Collect method is called.

    Si se llama al método sin parámetros GC.Collect() o se pasa otra sobrecarga GC.MaxGeneration como argumento, el montón de objetos grandes se recopilará junto con el resto del montón administrado.If the parameterless GC.Collect() method is called or another overload is passed GC.MaxGeneration as an argument, the LOH is collected along with the rest of the managed heap.

  • El sistema está en situación de memoria insuficiente.The system is in low memory situation.

    Esto sucede cuando el recolector de elementos no utilizados recibe una notificación de memoria alta del sistema operativo.This occurs when the garbage collector receives a high memory notification from the OS. Si el recolector de elementos no utilizados considera que llevar a cabo una recolección de elementos no utilizados de generación 2 va a ser productivo, activa una.If the garbage collector thinks that doing a generation 2 GC will be productive, it triggers one.

Implicaciones de rendimiento del montón de objetos grandesLOH Performance Implications

Las asignaciones del montón de objetos grandes afectan al rendimiento de las siguientes formas.Allocations on the large object heap impact performance in the following ways.

  • Costo de la asignación.Allocation cost.

    El CLR garantiza que se borra la memoria de cada nuevo objeto que envío.The CLR makes the guarantee that the memory for every new object it gives out is cleared. Esto significa que el costo de la asignación de un objeto grande está dominado por el borrado de la memoria (a menos que active una recolección de elementos no utilizados).This means the allocation cost of a large object is completely dominated by memory clearing (unless it triggers a GC). Si se tardan dos ciclos en borrar un byte, se tardarán 170 000 ciclos en borrar el objeto grande más pequeño.If it takes 2 cycles to clear one byte, it takes 170,000 cycles to clear the smallest large object. Borrar la memoria de un objeto de 16 MB en un equipo a 2 GHz tarda 16 ms aproximadamente.Clearing the memory of a 16MB object on a 2GHz machine takes approximately 16ms. Se trata de un costo bastante grande.That's a rather large cost.

  • Costo de la colección.Collection cost.

    Dado que el montón de objetos grandes y la generación 2 se recopilan juntos, si el umbral de uno de ellos se supera, se activa una recolección de generación 2.Because the LOH and generation 2 are collected together, if either one's threshold is exceeded, a generation 2 collection is triggered. Si se activa una colección de generación 2 debido al montón de objetos grandes, dicha generación no será necesariamente mucho menor después de la recolección de elementos no utilizados.If a generation 2 collection is triggered because of the LOH, generation 2 won't necessarily be much smaller after the GC. Si no hay muchos datos en la generación 2, el impacto será mínimo,If there's not much data on generation 2, this has minimal impact. pero si es grande, podría provocar problemas de rendimiento si se activan muchas recolecciones de elementos no utilizados de generación 2.But if generation 2 is large, it can cause performance problems if many generation 2 GCs are triggered. Si se asignan numerosos objetos grandes muy de vez en cuando y tiene un montón de objetos grandes muy voluminoso, podría estar dedicando demasiado tiempo a las recolecciones de elementos no utilizados.If many large objects are allocated on a very temporary basis and you have a large SOH, you could be spending too much time doing GCs. Además, el costo de la asignación puede subir tremendamente si sigue asignando y liberando objetos muy grandes.In addition, the allocation cost can really add up if you keep allocating and letting go of really large objects.

  • Elementos de matriz con tipos de referencia.Array elements with reference types.

    Por lo general, los objetos muy grandes en el montón de objeto grande son matrices (es muy poco habitual tener un objeto de instancia que sea realmente grande).Very large objects on the LOH are usually arrays (it's very rare to have an instance object that's really large). Si los elementos de una matriz contienen cuantiosas referencias, se incurrirá en un costo que no se produce cuando no existen tantas referencias en los elementos.If the elements of an array are reference-rich, it incurs a cost that is not present if the elements are not reference-rich. Si el elemento no contiene ninguna referencia, el recolector de elementos no utilizados no necesitará recorrer la matriz en absoluto.If the element doesn’t contain any references, the garbage collector doesn’t need to go through the array at all. Por ejemplo, si usa una matriz para almacenar nodos en un árbol binario, una forma de implementarlo es hacer referencia al nodo derecho e izquierdo de un nodo por medio de los nodos reales:For example, if you use an array to store nodes in a binary tree, one way to implement it is to refer to a node’s right and left node by the actual nodes:

    class Node
    {
       Data d;
       Node left;
       Node right;
    };
    
    Node[] binary_tr = new Node [num_nodes];
    

    Si num_nodes es grande, el recolector de elementos no utilizados debe recorrer al menos dos referencias por elemento.If num_nodes is large, the garbage collector needs to go through at least two references per element. Un método alternativo consiste en almacenar el índice de los nodos derecho e izquierdo:An alternative approach is to store the index of the right and the left nodes:

    class Node
    {
       Data d;
       uint left_index;
       uint right_index;
    } ;
    

    En lugar de hacer referencia a los datos del nodo izquierdo como left.d, haremos referencia a ellos como binary_tr[left_index].d.Instead of referring the left node’s data as left.d, you refer to it as binary_tr[left_index].d. Así, el recolector de elementos no utilizados no tiene que buscar el nodo izquierdo y el derecho en ninguna referencia.And the garbage collector doesn’t need to look at any references for the left and right node.

De estos tres factores, los dos primeros suelen ser más importantes que el tercero.Out of the three factors, the first two are usually more significant than the third. En consecuencia, se recomienda asignar un grupo de objetos grandes y reutilizarlos en vez de asignar temporales.Because of this, we recommend that you allocate a pool of large objects that you reuse instead of allocating temporary ones.

Recolección de datos de rendimiento para el montón de objetos grandesCollecting performance data for the LOH

Antes de recopilar datos de rendimiento para un área específica, conviene haber hecho ya lo siguiente:Before you collect performance data for a specific area, you should already have done the following:

  1. Haber encontrado pruebas de que se debe buscar en esta área.Found evidence that you should be looking at this area.

  2. Haber agotado otras áreas que conozca sin haber hallado una explicación al problema de rendimiento que ha visto.Exhausted other areas that you know of without finding anything that could explain the performance problem you saw.

Vea el blog Understand the problem before you try to find a solution (Conocer el problema antes de intentar buscar una solución) para más información sobre los conceptos básicos de memoria y la CPU.See the blog Understand the problem before you try to find a solution for more information on the fundamentals of memory and the CPU.

Puede usar las siguientes herramientas para recopilar datos sobre el rendimiento del montón de objetos grandes:You can use the following tools to collect data on LOH performance:

Contadores de rendimiento de memoria de .NET CLR.NET CLR Memory Performance counters

Estos contadores de rendimiento suelen ser un buen punto de partida para investigar los problemas de rendimiento (aunque se recomienda usar eventos ETW).These performance counters are usually a good first step in investigating performance issues (although we recommend that you use ETW events). Para configurar el Monitor de rendimiento, hay que agregar los contadores que se quiera, como se muestra en la figura 4.You configure Performance Monitor by adding the counters that you want, as Figure 4 shows. Los que son relevantes para el montón de objetos grandes son:The ones that are relevant for the LOH are:

  • Número de colecciones de gen. 2Gen 2 Collections

    Muestra el número de veces que se han producido recolecciones de elementos no utilizados de generación 2 desde que se inició el proceso.Displays the number of times generation 2 GCs have occurred since the process started. Este contador se incrementa al final de una recolección de elementos no utilizados de generación 2 (también llamada recolección completa de elementos no utilizados).The counter is incremented at the end of a generation 2 collection (also called a full garbage collection). Este contador muestra el último valor observado.This counter displays the last observed value.

  • Tamaño del montón del objeto grandeLarge Object Heap size

    Muestra el tamaño actual en bytes (espacio libre incluido) del montón de objetos grandes.Displays the current size, in bytes, including free space, of the LOH. Este contador se actualiza al final de una recolección de elementos no utilizados, no durante cada asignación.This counter is updated at the end of a garbage collection, not at each allocation.

Una forma habitual de examinar los contadores de rendimiento es a través del Monitor de rendimiento (perfmon.exe).A common way to look at performance counters is with Performance Monitor (perfmon.exe). Use "Agregar contadores" para agregar los contadores de interés relativos a los procesos que le preocupen.Use “Add Counters” to add the interesting counter for processes that you care about. Puede guardar los datos de contador de rendimiento en un archivo de registro, tal y como muestra la figura 4.You can save the performance counter data to a log file, as Figure 4 shows:

Captura de pantalla que muestra cómo agregar contadores de rendimiento.Screenshot that shows adding performance counters. Figura 4: Montón de objetos grandes después de una recolección de elementos no utilizados de generación 2Figure 4: The LOH after a generation 2 GC

Los contadores de rendimiento también se pueden consultar mediante programación.Performance counters can also be queried programmatically. Muchas personas los recolectan de esta forma como parte de su proceso rutinario de pruebas.Many people collect them this way as part of their routine testing process. Al detectar contadores con valores que no son normales, usan otros medios para obtener más detalles que ayuden en la investigación.When they spot counters with values that are out of the ordinary, they use other means to get more detailed data to help with the investigation.

Nota

El uso de eventos ETW es preferible al de los contadores de rendimiento, ya que ETW proporciona información mucho más completa.We recommend that you to use ETW events instead of performance counters, because ETW provides much richer information.

eventos ETWETW events

El recolector de elementos no utilizados proporciona un amplio conjunto de eventos ETW que sirven para entender qué hace el montón y por qué.The garbage collector provides a rich set of ETW events to help you understand what the heap is doing and why. En las siguientes entradas de blog se explica cómo recopilar y entender los eventos de recolección de elementos no utilizados con ETW:The following blog posts show how to collect and understand GC events with ETW:

Para identificar los excesos de recolecciones de elementos no utilizados de generación 2 provocados por las asignaciones temporales del montón de objetos grandes, busque en la columna Razón del desencadenador de las recolecciones.To identify excessive generation 2 GCs caused by temporary LOH allocations, look at the Trigger Reason column for GCs. Para realizar una sencilla prueba en la que solo se asignan objetos grandes temporales, puede recopilar información sobre los eventos ETW con la siguiente línea de comandos de PerfView:For a simple test that only allocates temporary large objects, you can collect information on ETW events with the following PerfView command line:

perfview /GCCollectOnly /AcceptEULA /nogui collect

El resultado es similar al siguiente:The result is something like this:

Captura de pantalla que muestra los eventos ETW en PerfView.Screenshot that shows ETW events in PerfView. Figura 5: Eventos ETW con PerfViewFigure 5: ETW events shown using PerfView

Como se puede ver, todas las recolecciones de elementos no utilizados pertenecen a la generación 2 y todas ellas se han activado por medio de AllocLarge, lo que significa que esta recolección de elementos no utilizados se ha activado a raíz de la asignación de un objeto grande.As you can see, all GCs are generation 2 GCs, and they are all triggered by AllocLarge, which means that allocating a large object triggered this GC. Sabemos que estas asignaciones son temporales porque la columna LOH Survival Rate % (% de tasa de supervivencia del montón de objetos grandes) muestra 1%.We know that these allocations are temporary because the LOH Survival Rate % column says 1%.

Se pueden recopilar más eventos ETW que indican quién ha asignado estos objetos grandes.You can collect additional ETW events that tell you who allocated these large objects. La siguiente línea de comandos:The following command line:

perfview /GCOnly /AcceptEULA /nogui collect

recopila un evento AllocationTick que se activa aproximadamente con cada asignación con un volumen de 100 000.collects an AllocationTick event which is fired approximately every 100k worth of allocations. Dicho de otro modo, se activa un evento cada vez que se asigna un objeto grande.In other words, an event is fired each time a large object is allocated. Así, puede consultar una de las vistas de asignación del montón de recolección de elementos no utilizados, en las que se muestran las pilas de llamadas que han asignado objetos grandes:You can then look at one of the GC Heap Alloc views which show you the callstacks that allocated large objects:

Captura de pantalla que muestra una vista de recolector de elementos no utilizados del montón.Screenshot that shows a garbage collector heap view. Figura 6: Vista de asignación del montón de recolección de elementos no utilizadosFigure 6: A GC Heap Alloc view

Como se puede ver, se trata de una prueba muy sencilla que simplemente asigna objetos grandes desde el método Main.As you can see, this is a very simple test that just allocates large objects from its Main method.

Un depuradorA debugger

Si todo lo que tiene es un volcado de memoria y necesita examinar los objetos que realmente hay en el montón de objetos grandes, puede usar la extensión de depurador SoS proporcionada por .NET.If all you have is a memory dump and you need to look at what objects are actually on the LOH, you can use the SoS debugger extension provided by .NET.

Nota

Los comandos de depuración que se mencionan en esta sección son válidos con los depuradores de Windows.The debugging commands mentioned in this section are applicable to the Windows Debuggers.

Aquí se muestra la salida de ejemplo resultante de analizar el montón de objetos grandes:The following shows sample output from analyzing the LOH:

0:003> .loadby sos mscorwks
0:003> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x013e35ec
sdgeneration 1 starts at 0x013e1b6c
generation 2 starts at 0x013e1000
ephemeral segment allocation context: none
segment   begin allocated     size
0018f2d0 790d5588 790f4b38 0x0001f5b0(128432)
013e0000 013e1000 013e35f8 0x000025f8(9720)
Large object heap starts at 0x023e1000
segment   begin allocated     size
023e0000 023e1000 033db630 0x00ffa630(16754224)
033e0000 033e1000 043cdf98 0x00fecf98(16699288)
043e0000 043e1000 05368b58 0x00f87b58(16284504)
Total Size 0x2f90cc8(49876168)
------------------------------
GC Heap Size 0x2f90cc8(49876168)
0:003> !dumpheap -stat 023e1000 033db630
total 133 objects
Statistics:
MT   Count   TotalSize Class Name
001521d0       66     2081792     Free
7912273c       63     6663696 System.Byte[]
7912254c       4     8008736 System.Object[]
Total 133 objects

El tamaño del montón de objetos grandes es (16 754 224 + 16 699 288 + 16 284 504) = 49 738 016 bytes.The LOH heap size is (16,754,224 + 16,699,288 + 16,284,504) = 49,738,016 bytes. Entre las direcciones 023e1000 y 033db630, 8 008 736 bytes están ocupados por una matriz de objetos System.Object; 6 663 696 bytes, por una matriz de objetos System.Byte, y 2 081 792 bytes, por espacio libre.Between addresses 023e1000 and 033db630, 8,008,736 bytes are occupied by an array of System.Object objects, 6,663,696 bytes are occupied by an array of System.Byte objects, and 2,081,792 bytes are occupied by free space.

En ocasiones, el depurador señala que el tamaño total del montón de objetos grandes es inferior a 85 000 bytes.Sometimes, the debugger shows that the total size of the LOH is less than 85,000 bytes. Esto sucede porque el propio módulo de tiempo de ejecución usa el montón de objetos grandes para asignar objetos con un tamaño menor que un objeto grande.This happens because the runtime itself uses the LOH to allocate some objects that are smaller than a large object.

Como el montón de objetos grandes no se compacta, a veces se considera que dicho montón es un origen de fragmentación.Because the LOH is not compacted, sometimes the LOH is thought to be the source of fragmentation. La fragmentación significa:Fragmentation means:

  • Fragmentación del montón administrado, que viene indicado por la cantidad de espacio libre entre los objetos administrados.Fragmentation of the managed heap, which is indicated by the amount of free space between managed objects. En SoS, el comando !dumpheap –type Free muestra la cantidad de espacio libre entre los objetos administrados.In SoS, the !dumpheap –type Free command displays the amount of free space between managed objects.

  • Fragmentación del espacio de direcciones de memoria virtual (VM), que es la memoria marcada como MEM_FREE.Fragmentation of the virtual memory (VM) address space, which is the memory marked as MEM_FREE. Se puede obtener usando varios comandos de depurador en windbg.You can get it by using various debugger commands in windbg.

    En el siguiente ejemplo se muestra la fragmentación en el espacio de memoria virtual:The following example shows fragmentation in the VM space:

    0:000> !address
    00000000 : 00000000 - 00010000
    Type     00000000
    Protect 00000001 PAGE_NOACCESS
    State   00010000 MEM_FREE
    Usage   RegionUsageFree
    00010000 : 00010000 - 00002000
    Type     00020000 MEM_PRIVATE
    Protect 00000004 PAGE_READWRITE
    State   00001000 MEM_COMMIT
    Usage   RegionUsageEnvironmentBlock
    00012000 : 00012000 - 0000e000
    Type     00000000
    Protect 00000001 PAGE_NOACCESS
    State   00010000 MEM_FREE
    Usage   RegionUsageFree
    … [omitted]
    -------------------- Usage SUMMARY --------------------------
    TotSize (     KB)   Pct(Tots) Pct(Busy)   Usage
    701000 (   7172) : 00.34%   20.69%   : RegionUsageIsVAD
    7de15000 ( 2062420) : 98.35%   00.00%   : RegionUsageFree
    1452000 (   20808) : 00.99%   60.02%   : RegionUsageImage
    300000 (   3072) : 00.15%   08.86%   : RegionUsageStack
    3000 (     12) : 00.00%   00.03%   : RegionUsageTeb
    381000 (   3588) : 00.17%   10.35%   : RegionUsageHeap
    0 (       0) : 00.00%   00.00%   : RegionUsagePageHeap
    1000 (       4) : 00.00%   00.01%   : RegionUsagePeb
    1000 (       4) : 00.00%   00.01%   : RegionUsageProcessParametrs
    2000 (       8) : 00.00%   00.02%   : RegionUsageEnvironmentBlock
    Tot: 7fff0000 (2097088 KB) Busy: 021db000 (34668 KB)
    
    -------------------- Type SUMMARY --------------------------
    TotSize (     KB)   Pct(Tots) Usage
    7de15000 ( 2062420) : 98.35%   : <free>
    1452000 (   20808) : 00.99%   : MEM_IMAGE
    69f000 (   6780) : 00.32%   : MEM_MAPPED
    6ea000 (   7080) : 00.34%   : MEM_PRIVATE
    
    -------------------- State SUMMARY --------------------------
    TotSize (     KB)   Pct(Tots) Usage
    1a58000 (   26976) : 01.29%   : MEM_COMMIT
    7de15000 ( 2062420) : 98.35%   : MEM_FREE
    783000 (   7692) : 00.37%   : MEM_RESERVE
    
    Largest free region: Base 01432000 - Size 707ee000 (1843128 KB)
    

Es más habitual ver fragmentación de la memoria virtual provocada por objetos grandes temporales que requieren que el recolector de elementos no utilizados adquiera frecuentemente nuevos segmentos de montón administrados del sistema operativo y liberar los vacíos para el sistema operativo.It’s more common to see VM fragmentation caused by temporary large objects that require the garbage collector to frequently acquire new managed heap segments from the OS and to release empty ones back to the OS.

Para comprobar si el montón de objetos grandes provoca fragmentación de la memoria virtual, puede establecer un punto de ruptura en VirtualAlloc y VirtualFree, y ver quién los llama.To verify whether the LOH is causing VM fragmentation, you can set a breakpoint on VirtualAlloc and VirtualFree to see who call them. Por ejemplo, para ver quién ha intentado asignar fragmentos de memoria virtual superiores a 8 MB desde el sistema operativo, podemos establecer un punto de ruptura como el siguiente:For example, to see who tried to allocate virtual memory chunks larger than 8MBB from the OS, you can set a breakpoint like this:

bp kernel32!virtualalloc "j (dwo(@esp+8)>800000) 'kb';'g'"

Este comando entra en el depurador y muestra la pila de llamadas solo si se llama a VirtualAlloc con un tamaño de asignación superior a 8 MB (0x800000).This command breaks into the debugger and shows the call stack only if VirtualAlloc is called with an allocation size greater than 8MB (0x800000).

En CLR 2.0 se ha incluido una característica denominada acumulación de memoria virtual que puede resultar útil si se encuentra en una situación en la que los segmentos (incluidos los de los montones de objetos pequeños y de objetos grandes) se adquieren y liberan con frecuencia.CLR 2.0 added a feature called VM Hoarding that can be useful for scenarios where segments (including on the large and small object heaps) are frequently acquired and released. Para establecer un valor de acumulación de memoria virtual, hay que especificar una marca de inicio denominada STARTUP_HOARD_GC_VM a través de la API de hospedaje.To specify VM Hoarding, you specify a startup flag called STARTUP_HOARD_GC_VM via the hosting API. En vez de liberar los segmentos vacíos para el sistema operativo, CLR anula la confirmación de la memoria de estos segmentos y los coloca en una lista en esperaInstead of releasing empty segments back to the OS, the CLR decommits the memory on these segments and puts them on a standby list. (cabe decir que CLR no lleva esto a cabo en segmentos que son demasiado grandes). Más adelante, CLR usa esos segmentos para cumplir nuevas solicitudes de segmento.(Note that the CLR doesn't do this for segments that are too large.) The CLR later uses those segments to satisfy new segment requests. La próxima vez que la aplicación necesite un nuevo segmento, CLR usará uno de esta lista en espera (si encuentra uno lo suficientemente grande).The next time that your app needs a new segment, the CLR uses one from this standby list if it can find one that’s big enough.

La acumulación de memoria virtual también es útil en aplicaciones que quiere retener los segmentos ya adquiridos; es el caso, por ejemplo, de algunas aplicaciones de servidor que son aplicaciones dominantes que se ejecutan en el sistema, para evitar que se produzcan excepciones por memoria insuficiente.VM hoarding is also useful for applications that want to hold onto the segments that they already acquired, such as some server apps that are the dominant apps running on the system, to avoid out of memory exceptions.

Se recomienda encarecidamente tener cuidado al usar esta característica en la aplicación y garantizar que esta tiene un uso de memoria bastante estable.We strongly recommend that you carefully test your application when you use this feature to ensure your application has fairly stable memory usage.