Este artículo proviene de un motor de traducción automática.
C#, Visual Basic y C++
Administración de la memoria en aplicaciones de la Tienda Windows, parte 2
[Calle Chipalo]
[Dan Taylor]
En el Windows 8 edición especial de MSDN Magazine, el primer artículo de esta serie discute cómo se producen pérdidas de memoria, por qué se ralentizar su aplicación y degrada la experiencia global del sistema, métodos generales para evitar fugas, y cuestiones específicas que se han encontrado problemático en aplicaciones JavaScript (ver "Gestión de memoria en tienda de aplicaciones de Windows," msdn.microsoft.com/magazine/jj651575). Ahora analizaremos pérdidas de memoria en el contexto de aplicaciones de C#, Visual Basic y C++. Analizaremos algunas formas básicas de fugas han ocurrido en generaciones pasadas de apps, y cómo las tecnologías de Windows 8 le ayudan a evitar estas situaciones. Con esta Fundación, trasladaremos a escenarios más complejos que pueden causar su aplicación a la memoria de la fuga. Vamos a llegar a ella!
Ciclos simples
En el pasado, muchas fugas fueron causadas por ciclos de referencia. Objetos involucrados en el ciclo siempre tendría una referencia activa aunque nunca se llegara a los objetos en el ciclo. La referencia activa mantendría los objetos vivos para siempre, y si un programa creado estos ciclos con frecuencia, continuaría fugas de memoria en el tiempo.
Un ciclo de referencia puede ocurrir por múltiples razones. El más obvio es cuando objetos explícitamente referencia mutuamente. Por ejemplo, se produce el siguiente código en la imagen en figura 1:
Foo a = new Foo();
Bar b = new Bar();
a.barVar = b;
b.fooVar = a;
Figura 1 referencia Circular
Afortunadamente, en recolección lenguajes como C#, JavaScript y Visual Basic, este tipo de referencia circular se automáticamente limpian una vez que las variables ya no son necesarios.
C + + CX, por el contrario, no utiliza la recolección de basura. En cambio, se basa en recuentos de referencias para realizar la administración de memoria. Esto significa que el sistema se reclame objetos sólo cuando tienen cero referencias activas. En estos lenguajes, los ciclos entre estos objetos obligaría A y B a vivir para siempre porque nunca tendrían referencias cero. Peor aún, todo se hace referencia por un y B viviría para siempre así. Este es un ejemplo simplificado que puede evitarse fácilmente al escribir programas básicos; Sin embargo, programas complejos pueden crear ciclos que implican varios objetos encadenar juntos en formas no evidentes. Echemos un vistazo a algunos ejemplos.
Ciclos con los controladores de eventos
Como se explica en el artículo anterior, los controladores de eventos son una manera muy común para referencias circulares a crearse. Figura 2 muestra cómo esto puede ocurrir.
Figura 2 causando una referencia Circular con un controlador de eventos
<MainPage x:Class="App.MainPage" ...>
...
<TextBlock x:Name="displayTextBlock" .../>
<Button x:Name="myButton" Click="ButtonClick" .../>
...
</MainPage>
public sealed partial class MainPage : Page
{
...
private void ButtonClick(object sender, RoutedEventArgs e) {
DateTime currentTime = DateTime.Now;
this.displayTextBlock.Text = currentTime.ToString();
}
...
}
Aquí simplemente hemos añadido un botón y un TextBlock a una página. También hemos configurado un controlador de eventos definido en la clase de página, para el evento Click del botón. Este controlador actualiza el texto en el TextBlock para mostrar la hora actual cuando se hace clic en el botón. Como veremos, incluso este sencillo ejemplo tiene una referencia circular.
El botón y TextBlock son hijos de la página y por lo tanto, la página debe tener una referencia a ellos, como se muestra en el diagrama superior figura 3.
Figura 3 referencia Circular relacionada con el controlador de eventos
En el diagrama de abajo, otra referencia se crea por el registro del controlador de eventos, que se define en la clase de página.
El origen del evento (botón) tiene una fuerte referencia al controlador de eventos, un método de delegado, para que la fuente pueda llamar al controlador de eventos cuando se desencadena el evento. Vamos a llamar a este delegado un delegado fuerte porque la referencia de ella es fuerte.
Ahora tenemos una referencia circular. Una vez que el usuario se desplaza fuera de la página, el recolector (GC) es lo suficientemente inteligente como para recuperar el ciclo entre la página y el botón. Estos tipos de referencias circulares se limpiarán automáticamente cuando ya no son necesarios si estás escribiendo aplicaciones en JavaScript, C# o Visual Basic. Como mencionamos anteriormente, sin embargo, C + + / CX es un lenguaje ref contados, lo que significa objetos se eliminan automáticamente sólo cuando el número de referencia cae a cero. Aquí, las referencias fuertes creadas obligaría a la página y botón para vivir para siempre porque nunca tendrían cero referencia cuenta. Peor aún, todos los elementos que contiene la página (potencialmente un árbol de elementos muy grandes) vivirían para siempre así porque la página contiene referencias a todos estos objetos.
Por supuesto, la creación de curvas de evento es un escenario muy común y Microsoft no quiere que esto provocar fugas en su aplicación independientemente del idioma que se utiliza. Por esa razón el compilador XAML hace la referencia del delegado para el detector de eventos una referencia débil. Puede considerar esto como un delegado débil porque la referencia del delegado es una referencia débil.
El delegado débil garantiza que la página no es mantenida viva por la referencia del delegado a la página. La referencia débil no contará contra el contador de referencias de la página y así le permitirá ser destruido una vez que todas las demás referencias caen a cero. Posteriormente, el botón, TextBlock y cualquier otra referencia a la página serán destruidos así.
Orígenes de eventos de larga duración
A veces un objeto con una larga vida útil define eventos. Nos referimos a estos eventos como larga vida porque los eventos comparten la vida del objeto que las define. Estos eventos de larga duración tengan referencias a todos los controladores registrados. Esto obliga a los controladores y los objetos dirigidos por los controladores, para sobrevivir tanto tiempo como el origen del evento de larga duración.
En la sección de "Controlador de eventos" del artículo de fuga de memoria anterior, analizamos un ejemplo de ello. Cada página de una aplicación registra para SizeChangedEvent de la ventana de aplicación. La referencia de SizeChangedEvent de la ventana al controlador de eventos en la página mantendrá cada instancia de página viva como ventana de la aplicación es alrededor. Todas las páginas que han sido desplazadas para permanecer vivo aunque sólo uno de ellos está a la vista. Esta fuga se fija fácilmente por anular el registro de controlador de SizeChangedEvent de cada página cuando el usuario navega lejos de la página.
En este ejemplo, es claro cuando la página ya no es necesaria y el desarrollador es capaz de eliminar el controlador de eventos de la página. Lamentablemente no siempre es fácil razonar sobre la duración de un objeto. Considere la posibilidad de simular a un "delegado débil" en C# o Visual Basic, si encuentras las pérdidas causadas por eventos de larga vida a objetos a través de controladores de eventos. (Ver "Simulación 'Delegados débiles' en CLR" en bit.ly/SUqw72.) El patrón de delegado débil coloca un objeto intermedio entre el origen del evento y el controlador de eventos. Utilice una referencia fuerte desde el origen del evento para el objeto intermedio y una referencia débil desde el objeto intermedio al controlador de eventos, como se muestra en figura 4.
Figura 4 utilizando un objeto intermedio entre el origen del evento y el detector de eventos
En el diagrama superior figura 4, LongLivedObject expone EventA y ShortLivedObject registros EventAHandler para controlar el evento. LongLivedObject tiene una mucho mayor vida útil que efímeroobjeto y la referencia fuerte entre EventA y EventAHandler mantener ShortLivedObject vivo tanto tiempo como LongLivedObject. Colocar un IntermediateObject entre LongLivedObject y ShortLivedObject (como se muestra en el diagrama inferior) permite IntermediateObject a ser filtrado en lugar de ShortLivedObject. Se trata de una pérdida mucho menor porque el IntermediateObject necesita exponer una única función, mientras que ShortLivedObject puede contener estructuras de datos de gran tamaño o un árbol visual complejo.
Echemos un vistazo a cómo podría aplicarse un delegado débil en el código. Un evento puede registrarse para muchas clases es pantallaProperties.OrientationChanged. DisplayProperties es realmente una clase estática, así que el evento OrientationChanged será todo para siempre. El evento tendrá una referencia a cada objeto que se utiliza para escuchar el evento. En el ejemplo representado en figura 5 y figura 6, la clase LargeClass utiliza el patrón de delegado débiles para garantizar que el evento de OrientationChanged contiene una referencia fuerte sólo a una clase intermedia cuando se registra un controlador de eventos. La clase intermedia, a continuación, llama al método, definido en LargeClass, lo que hace realmente el trabajo necesario cuando se desencadena el evento OrientationChanged.
Figura 5 el patrón de delegado débil
Figura 6 implementar a un delegado débil
public class LargeClass
{
public LargeClass()
{
// Create the intermediate object
WeakDelegateWrapper wrapper = new WeakDelegateWrapper(this);
// Register the handler on the intermediate with
// DisplayProperties.OrientationChanged instead of
// the handler on LargeClass
Windows.Graphics.Display.DisplayProperties.OrientationChanged +=
wrapper.WeakOrientationChangedHandler;
}
void OrientationChangedHandler(object sender)
{
// Do some stuff
}
class WeakDelegateWrapper : WeakReference<LargeClass>
{
DisplayPropertiesEventHandler wrappedHandler;
public WeakDelegateWrapper(LargeClass wrappedObject,
DisplayPropertiesEventHandler handler) : base(wrappedObject)
{
wrappedHandler = handler;
wrappedHandler += WeakOrientationChangedHandler;
}
public void WeakOrientationChangedHandler(object sender)
{
LargeClass wrappedObject = Target;
// Call the real event handler on LargeClass if it still exists
// and has not been garbage collected.</span>
<span class="sentence">Remove the event handler
// if LargeClass has been garbage collected so that the weak
// delegate no longer leaks
if(wrappedObject != null)
wrappedObject.OrientationChangedHandler(sender);
else
wrappedHandler -= WeakOrientationChangedHandler;
}
}
}
Lambdas
Muchas personas les resulta más fácil de implementar controladores de eventos con una lambda — o función en línea — en lugar de un método. Vamos a convertir el ejemplo de figura 2 para hacer exactamente eso (ver figura 7).
Figura 7 implementar un controlador de eventos con una Lambda
<MainPage x:Class="App.MainPage" ...>
...
<TextBlock x:Name="displayTextBlock" ...
/>
<Button x:Name="myButton" ...
/>
...
</MainPage>
public sealed partial class MainPage : Page
{
...
protected override void OnNavigatedTo
{
myButton.Click += => (source, e)
{
DateTime currentTime = DateTime.Now;
this.displayTextBlock.Text = currentTime.ToString();
}
...
}
Usando un lambda también crea un ciclo. Las primeras referencias todavía obviamente se crean desde la página el botón y el TextBlock (como el diagrama superior figura 3).
El siguiente conjunto de referencias, se muestra en la figura 8, invisible es creado por la expresión lambda. Evento Click del botón está conectado a un objeto RoutedEventHandler cuyo método Invoke es implementado por un cierre en un objeto interno creado por el compilador. El cierre debe contener referencias a todas las variables que se hace referencia a la expresión lambda. Una de estas variables es "esto", que — en el contexto de la expresión lambda — se refiere a la página, creando así el ciclo.
Figura 8 referencias creadas por el Lambda
Si la expresión lambda está escrita en C# o Visual Basic, el GC de CLR se recupere los recursos involucrados en este ciclo. Sin embargo, en C + + / CX este tipo de referencia es una referencia fuerte y puede causar una fuga. Esto no significa que todas las lambdas en C + + / fuga CX. Una referencia circular no habría creada si no hubiésemos hace referencia a "esto" y sólo utiliza variables locales al cierre al definir la expresión lambda. Como una solución a este problema, si usted necesita acceder a una variable externa al cierre en un controlador de eventos en línea, aplicar ese controlador de eventos como un método en su lugar. Esto permite al compilador XAML crear una referencia débil del evento al controlador de eventos y la memoria se ser reclamada. Otra opción es utilizar la sintaxis de puntero a miembro, que le permite especificar si se toma una referencia fuerte o débil contra la clase que contiene el método de puntero a miembro (en este caso, la página).
Utilice el parámetro de remitente del evento
Como se explica en el artículo anterior, cada controlador de eventos recibe un parámetro, normalmente llamado "remitente", que representa el origen del evento. El parámetro source de evento de un lambda ayuda a evitar referencias circulares. Vamos a modificar nuestro ejemplo (usando C + + CX) por lo que el botón muestra la hora actual cuando se hace clic en (ver figura 9).
Figura 9 el botón Mostrar la hora actual
<MainPage x:Class="App.MainPage" ...>
...
<Button x:Name="myButton" ...
/>
...
</MainPage>
MainPage::MainPage()
{
...
myButton->Click += ref new RoutedEventHandler(
[this](Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ e)
{
Calendar^ cal = ref new Calendar();
cal->SetToNow() ;
this->myButton->Content = cal->SecondAsString();
});
...
El lambda actualizado crea las mismas referencias circulares se muestra en la figura 8. Causarán C + + / CX a la fuga, pero esto puede evitarse mediante el parámetro source en el lugar de referencia myButton a través de la variable de "este". Cuando se ejecuta el método de cierre, crea los parámetros de "origen" y "e" en la pila. Estas variables viven sólo para la duración de la llamada de método, en lugar de para siempre y cuando la expresión lambda está conectada al controlador de eventos del botón (currentTime tiene la misma vida útil). Aquí está el código para utilizar el parámetro source:
MainPage::MainPage()
{
...
myButton->Click += ref new RoutedEventHandler([](Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ e)
{
DateTime currentTime ;
Calendar^ cal = ref new Calendar();
cal->SetToNow() ;
Button ^btn = (Button^)sender ;
btn->Content = cal->SecondAsString();
});
...
}
Las referencias parecen ahora lo que se muestra en la figura 10. La referencia que se representan en rojo, creando el ciclo, está presente sólo durante la ejecución del controlador de eventos. Esta referencia es destruida una vez que haya completado el controlador de eventos y estamos dejamos sin ciclos que pueden provocar una fuga.
Figura 10 utilizando el parámetro Source
Uso WRL para evitar fugas en el código de C++ estándar
Puede utilizar el estándar de C++ para crear aplicaciones Windows Store, además de JavaScript, C#, C + + / CX y Visual Basic. Al hacerlo, conocida COM son técnicas, como referencia contando administrar la duración de los objetos y valores HRESULT para determinar si una operación tuvo éxito o no de pruebas. Biblioteca de plantillas de C++ Runtime (WRL) de Windows simplifica el proceso de escribir este código (bit.ly/P1rZrd). Recomendamos que utilizarlo cuando se implementa el estándar C++ Windows Store apps para reducir los errores y pérdidas de memoria, que pueden ser muy difíciles de localizar y resolver.
Utilice controladores de eventos que cruzan las fronteras del idioma con precaución
Por último, hay un patrón de codificación que requiere atención especial. Hemos analizado la posibilidad de fugas de referencias circulares que implican los controladores de eventos, y que muchos de estos casos pueden ser detectados y evitar factores atenuantes de la plataforma suministrada. Estas mitigaciones no aplican cuando el ciclo cruza varios montones de recolección.
Echemos un vistazo de cómo esto puede suceder, como se muestra en figura 11.
Figura 11 muestra la ubicación del usuario
<Page x:Class="App.MainPage" ...>
...
<TextBlock x:Name="displayTextBlock" ...
/>
...
</Page>
public sealed partial class MyPage : Page
{
...
Geolocator gl;
protected override void OnNavigatedTo{} ()
{
Geolocator gl = new Geolocator();
gl.PositionChanged += UpdatePosition;
}
private void UpdatePosition(object sender, RoutedEventArgs e)
{
// Change the text of the TextBlock to reflect the current position
}
...
}
Este ejemplo es muy similar a los ejemplos anteriores. Una página contiene un TextBlock que muestra un poco de información. En esta muestra, sin embargo, el TextBlock muestra la ubicación del usuario, como se muestra en figura 12.
Figura 12 referencias circulares abarcan un límite de recolector de basura
En este punto, probablemente podría ha atraído las referencias circulares usted mismo. Lo que no es obvio, sin embargo, es que las referencias circulares abarcan un límite de recolector de basura. Porque las referencias se extienden fuera de CLR, el GC de CLR no puede detectar la presencia de un ciclo y esto tendrá fugas. Es difícil evitar estos tipos de fugas porque siempre se puede saber en qué idioma se aplican a un objeto y sus eventos. Si geolocalizador está escrito en C# o Visual Basic, las referencias circulares permanecerá dentro del CLR y el ciclo de recolección. Si la clase está escrita en C++ (como en este caso) o JavaScript, el ciclo hará una fuga.
Hay varias maneras de asegurar que su aplicación no se ve afectada por filtraciones como este. En primer lugar, usted no necesita preocuparse acerca de estas fugas si estás escribiendo una pura aplicación de JavaScript. El GC de JavaScript a menudo es lo suficientemente inteligente como para seguir las referencias circulares a través de todos los objetos de WinRT. (Ver la artículo anterior para más detalles sobre la administración de la memoria de JavaScript.)
Usted también no necesita preocuparse si está registrando para eventos sobre objetos que se sabe son en el marco XAML. Esto significa algo en el espacio de nombres Windows.UI.Xaml e incluye todas las clases FrameworkElement, UIElement y Control familiares. El GC de CLR es lo suficientemente inteligente como para seguir las referencias circulares a través de objetos XAML.
La otra forma de tratar este tipo de fuga es eliminar el controlador de eventos cuando ya no es necesario. En este ejemplo, puede eliminar del registro el controlador de eventos en el evento OnNavigatedFrom. La referencia creada por el controlador de eventos sería eliminada y todos los objetos se destruiría. Tenga en cuenta que no es posible anular el registro de lambdas, así manejar un evento con una lambda puede causar fugas.
Análisis de pérdidas de memoria en aplicaciones de la tienda de Windows usando C# y Visual Basic
Si estás escribiendo un app Store ventana en C# o Visual Basic, es útil tener en cuenta que muchas de las técnicas discutieron en el artículo anterior JavaScript se aplican a C# y Visual Basic también. En particular, el uso de referencias débiles es una manera común y eficaz para reducir el crecimiento de la memoria (ver bit.ly/S9gVZW para obtener más información), y los patrones de arquitectura "Tire" y "Inflar" aplicarán igualmente bien.
Ahora vamos a tomar un vistazo de cómo usted puede encontrar y arreglar comunes fugas utilizando herramientas disponibles actualmente: Administrador de tareas de Windows y una generación de perfiles de código administrado de la herramienta llamada PerfView, disponible para su descarga en bit.ly/UTdb4M.
En la sección de "Controladores de eventos" de la artículo anterior en fugas, vimos un ejemplo llamado LeakyApp (repetida en figura 13 para su comodidad), lo que provoca una pérdida de memoria en el controlador de eventos de su ventana SizeChanged.
Figura 13 LeakyApp
public sealed partial class ItemDetailPage : LeakyApp.Common.LayoutAwarePage
{
public ItemDetailPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Window.Current.SizeChanged += WindowSizeChanged;
}
private void WindowSizeChanged(object sender,
Windows.UI.Core.WindowSizeChangedEventArgs e)
{
// Respond to size change
}
// Other code
}
En nuestra experiencia, este es el tipo más común de fuga en código de Visual Basic y C#, pero las técnicas que describiremos aplicarán igual evento circular fugas y al crecimiento de la estructura de datos ilimitada. Echemos un vistazo sobre cómo usted puede encontrar y arreglar fugas en sus propias aplicaciones utilizando herramientas disponibles hoy en día.
Buscando crecimiento de memoria
El primer paso en la fijación de una pérdida de memoria es identificar el crecimiento constante de la memoria de las operaciones que debe ser neutral de la memoria. En la sección de "Descubrir pérdidas de memoria" de la artículo anterior, hablamos de una manera muy simple que se puede utilizar el administrador de tareas de Windows integrada para vigilar el crecimiento de la Total de trabajo conjunto (TWS) de una aplicación mediante la ejecución a través de un escenario varias veces. En la aplicación de ejemplo, los pasos para causar una fuga de memoria son hacer clic en una ficha y a continuación, vaya a la página de inicio.
En figura 14, la captura de pantalla superior muestra el conjunto de trabajo en el administrador de tareas antes 10 iteraciones de estos pasos, y la captura de pantalla inferior muestra esto después de 10 iteraciones.
Figura 14 viendo para el crecimiento de la memoria
Después de 10 iteraciones, puede ver la cantidad de memoria utilizada ha crecido de K 44.404 a 108, 644 K. Esto definitivamente me parece una pérdida de memoria, y nosotros debemos cavar más.
Adición de determinismo de GC
Para estar seguro de que tenemos una fuga de memoria en nuestras manos, tenemos que confirmar que persiste después de la limpieza de colección de basura completa. La GC utiliza un conjunto de heurística para decidir el mejor momento para correr y recuperar la memoria de los muertos, y generalmente lo hace un buen trabajo. Sin embargo, en cualquier momento podría haber un número de objetos "muertos" en la memoria que aún no han sido recogidos. Forma determinista llamando a la GC nos permite separar el crecimiento causado por la colección lento y crecimiento causado por fugas de verdaderas, y borra la imagen cuando miramos a investigar lo que realmente son fugas objetos.
La forma más sencilla de hacerlo es utilizar el botón "Fuerza GC" desde PerfView, se muestra en la siguiente sección de las instrucciones de tomar una instantánea del montón. Otra opción es agregar un botón a la aplicación que activará el GCs mediante código. El siguiente código inducirá una recolección:
private void GCButton_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
La WaitForPendingFinalizers y posterior llamada por cobrar aseguran que así se recopilarán cualquier objetos liberados como resultado de los finalizadores.
En la aplicación de ejemplo, sin embargo, al pulsar este botón después de 10 iteraciones liberadas sólo 7 MB de 108 MB de trabajo. En este punto podemos estar bastante seguros que hay una fuga de memoria en LeakyApp. Ahora, tenemos que buscar la causa de la pérdida de memoria en nuestro código administrado.
Análisis de crecimiento de la memoria
Ahora podrá utilizar PerfView para tomar un diff de montón de GC de CLR y analizar el diff para encontrar los objetos se filtró.
Para averiguar donde está siendo filtrado memoria, usted querrá tomar una instantánea del montón antes y después de ejecutar a través de una fuga -provocando la acción en su aplicación. PerfView puede diff los dos instantáneas para encontrar donde está el crecimiento de la memoria.
Para tomar una instantánea del montón con PerfView:
- Abra PerfView.
- Haga clic en memoria en la barra de menús.
- Haga clic en tomar instantánea del montón (ver figura 15).
- Seleccione su app Store de Windows en la lista.
- Haga clic en el botón de "Fuerza GC" para inducir un GC dentro de la aplicación.
- Establecer el nombre de archivo para la descarga que desea guardar y haga clic en el montón de GC de descarga (ver figura 16).
Figura 15 tomar una instantánea del montón
Figura 16 Dumping el montón de GC
Un volcado del montón administrado se guardará en el archivo especificado y PerfView se abrirá una pantalla del archivo de volcado que muestra una lista de todos los tipos en el montón administrado. Una investigación de fuga de memoria, debe eliminar el contenido de los cuadros de texto de FoldPats y % de doblez y haga clic en el botón Actualizar. En la vista resultante, la columna de Exc muestra el tamaño total en bytes que tipo se utiliza en el montón de GC y la columna de Exc Ct muestra el número de casos de ese tipo en el montón de GC.
Figura 17 muestra una vista de un volcado de GC para LeakyApp.
Figura 17 montón instantánea en PerfView
Para obtener un diff de dos instantáneas de montón que muestra la pérdida de memoria:
- Pase a través de unas cuantas iteraciones de la acción que provoca la pérdida de memoria en su aplicación. Esto incluirá objetos inicializados perezoso cargado ni una sola vez en la línea de referencia.
- Tomar una instantánea del montón, incluso forzando un GC para quitar objetos muertos. Nos referiremos a esto como la instantánea "antes".
- Recorren varias iteraciones más de su fuga -provocando la acción.
- Tome otra instantánea del montón, incluso forzando un GC para quitar objetos muertos. Esta será la instantánea "después".
- Desde la vista de la instantánea después, haga clic en el elemento de menú de Diff y seleccione el antes instantánea como su base. Asegúrese de tener su vista abierta desde el antes instantánea, o no se mostrará en el menú de diff.
- Se mostrará una nueva ventana que contiene el diff. Eliminar el contenido de los cuadros de texto de FoldPats y % de doblez y actualizar la vista.
Ahora tiene una vista que muestra el crecimiento de objetos administrados entre las dos instantáneas de su montón administrado. Para LeakyApp, tomamos el antes instantánea después de tres iteraciones y el después después de 13 iteraciones, dando la diferencia en el montón de GC después de 10 iteraciones. El diff de instantánea del montón de PerfView se muestra en la figura 18.
Figura 18 el Diff de dos instantáneas de PerfView
La columna de Exc da el aumento en el tamaño total de cada tipo en el montón administrado. Sin embargo, la columna de Exc Ct mostrará la suma de las instancias en las instantáneas de dos montón en lugar de la diferencia entre los dos. Esto es no lo que cabría esperar de este tipo de análisis, y las versiones futuras de PerfView le permitirá ver esta columna como una diferencia; por ahora, simplemente ignore la columna Exc Ct al utilizar la vista de diff.
Los tipos que se filtraron entre las dos instantáneas tendrá un valor positivo en la columna de Exc, pero determinar qué objeto es impedir que objetos que se recogen se llevará algún tipo de análisis.
Analizando el Diff
Basado en sus conocimientos de la aplicación, debe mirar la lista de objetos en el diff y encontrar cualquier tipo usted no esperaría que crecer con el tiempo. Examinar los tipos que se definen en su aplicación en primer lugar, porque una fuga es probable que sea el resultado de una referencia que celebra a su código de aplicación. El próximo lugar para buscar es en tipos de filtrado en el espacio de nombres Windows.UI.Xaml, ya que estos son probables que se celebrará al código de aplicación así. Si miramos primeros en tipos definidos solamente en nuestra aplicación, el tipo de ItemDetailPage se muestra en la parte superior de la lista. Es el objeto más grande de filtrado definido en nuestra aplicación de ejemplo.
Haga doble clic en un tipo en la lista le llevará a la "se refiere-de" (sic) vista para ese tipo. Esta vista muestra un árbol de referencia de todos los tipos que contienen referencias a ese tipo. Puede expandir el árbol para pasar por todas las referencias que mantienen vivo ese tipo. En el árbol, un valor de [CCW (ObjectType)] significa que el objeto se mantiene vivo por una referencia desde fuera de código administrado (como el marco XAML, código C++ o JavaScript). Figura 19 muestra una captura de pantalla del árbol de referencia para nuestro objeto sospechoso de ItemDetailPage.
Figura 19 el árbol de referencia para el tipo de ItemDetailPage en PerfView
Desde este punto de vista puede ver claramente que la ItemDetailPage es se mantenía vivo por el controlador de eventos para el evento WindowSizeChanged, y esto es probablemente la causa de la pérdida de memoria. El controlador de eventos se celebra a por algo fuera de código administrado, en este caso el marco XAML. Si nos fijamos en uno de los objetos XAML, puede ver que está también siendo mantenidos vivos por el mismo controlador de eventos. Por ejemplo, se muestra el árbol de referencia para el tipo de Windows.UI.Xaml.Controls.Button en figura 20.
Figura 20 el árbol de referencia para el tipo de Windows.UI.Xaml.Controls.Button
Desde este punto de vista, se puede ver que todas las instancias nuevas de interfaz de usuario.Xaml.Controls.Button se mantiene vivo por ItemDetailPage, que a su vez se mantiene viva por el WindowSizeChangedEventHandler.
Es bastante claro en este punto que para arreglar la fuga de memoria tenemos que eliminar la referencia desde el controlador de evento SizeChanged a ItemDetailPage, así:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
Window.Current.SizeChanged -= WindowSizeChanged;
}
Después de agregar este rebase a la clase de ItemDetailPage, las instancias de ItemDetailPage ya no se acumulan con el tiempo y se fija nuestra fuga.
Los métodos descritos aquí dan algunas maneras simples para analizar las pérdidas de memoria. No te sorprendas si te encuentras con situaciones similares. Es muy común para fugas de memoria en aplicaciones de Windows tienda causada por suscribirse a orígenes de eventos de larga duración y no darse de baja de ellos; Afortunadamente, la cadena de filtrado objetos identificará claramente el problema. Esto abarca también los casos de referencias circulares en los controladores de eventos a través de idiomas, así como tradicionales C# / pérdidas de memoria básica Visual causado por estructuras de datos ilimitada para almacenar en caché.
En casos más complejos, pérdidas de memoria pueden ser causados por ciclos entre objetos de aplicaciones con C#, Visual Basic, JavaScript y C++. Estos casos pueden ser difíciles de analizar porque muchos objetos en el árbol de referencia se mostrarán como externos al código administrado.
Consideraciones para Apps Store de Windows que utilizan ambos JavaScript y C# o Visual Basic
Para una aplicación que se construye en JavaScript y utiliza C# o Visual Basic para implementar componentes subyacentes, es importante recordar que habrá dos GCs separados dirigiendo dos montones separados. Naturalmente, esto aumentará la memoria utilizada por la aplicación. Sin embargo, el factor más importante en el consumo de memoria de la aplicación seguirá siendo su gestión de estructuras de datos de gran tamaño y sus vidas. Hacerlo en todos los idiomas significa que hay que tener en mente lo siguiente:
Medir el impacto de limpieza retrasado una pila de recolección normalmente contiene objetos coleccionables, esperando la próxima GC. Puede utilizar esta información para investigar el uso de la memoria de su aplicación. Si se mide la diferencia en el uso de la memoria antes y después de una recolección manualmente inducidas, puede ver cuánta memoria estaba esperando «diferida limpieza» versus la memoria utilizada por los objetos "en vivo".
Para aplicaciones de doble-GC, es muy importante comprender este delta. Debido a las referencias entre montones, podría llevar una secuencia de recolección para eliminar todos los objetos coleccionables. Para probar este efecto y para borrar su TWS para que queden sólo los objetos activos, usted debería inducir repetidas, alternando GCs en el código de prueba. Puede desencadenar un GC en respuesta a un clic de botón (por ejemplo), o mediante una herramienta de análisis de rendimiento lo admite. Para activar un GC en JavaScript, utilice el siguiente código:
window.CollectGarbage();
Para el CLR, utilice la siguiente:
GC.Collect(2, GCCollectionMode.Optimized);
GC.WaitForPendingFinalizers();
Usted habrá notado que para el CLR utilizamos sólo un GC.Recoger la llamada, a diferencia del código para inducir un GC en la sección sobre cómo diagnosticar pérdidas de memoria. Esto es porque en este caso queremos simular los patrones reales de GC en su aplicación que emitirá sola GC en un momento, mientras que previamente hemos querido probar y limpiar los objetos tantas como sea posible. Nota que la función de GC de la fuerza de PerfView no debe utilizarse para medir el retrasada de limpieza, porque pueden forzar un JavaScript y un GC de CLR.
La misma técnica debe utilizarse para medir el uso de memoria por suspender. En C# - o JavaScript - entorno único, que GC de idioma se ejecutará automáticamente en suspender. Sin embargo, en C# o Visual Basic y JavaScript aplicaciones híbridas, funcionará sólo el GC de JavaScript. Esto podría dejar algunos artículos coleccionables en el montón CLR que aumentará el conjunto de trabajo privado de su aplicación (PWS) mientras están suspendidos. Dependiendo de cuan grandes son estos elementos, su aplicación podría ser prematuramente en lugar de estar suspendido (consulte la sección "Evitar sosteniendo grandes referencias sobre suspender" de la artículo anterior).
Si el impacto en su PWS es muy grande, cabe invocar la GC de CLR en el controlador de la suspensión. Sin embargo, nada de esto debe hacerse sin medir una reducción sustancial en el consumo de memoria, porque en general desea mantener el trabajo realizado en suspender a un mínimo (y en particular, ni de cerca los 5 segundo tiempo límite impuesta por el sistema).
Analizar tanto montones al investigar el consumo de memoria y después eliminando cualquier impacto de limpieza retrasado, es importante analizar el montón de JavaScript y el montón. net. Para el montón. NET, el método recomendado es utilizar la herramienta PerfView, descrita en la sección de "analizar memoria fugas en Windows Store Apps usando C# y Visual Basic", si desea conocer el consumo de memoria total o para investigar una fuga.
Con la versión actual de PerfView, eres capaz de mirar una vista combinada de JavaScript y .NET montones, lo que le permite ver todos los objetos a través de lenguajes administrados y entender las referencias entre ellos.
Calle Chipalo es un director de programa de Windows 8 XAML. Comenzó a trabajar en Windows Presentation Foundation (WPF) hacia afuera del colegio. Cinco años más tarde ha ayudado XAML evolucionan a través de tres productos (WPF, Silverlight y Windows 8 XAML) y múltiples versiones. Durante el desarrollo de Windows 8, poseía todo lo relacionado con texto, impresión y rendimiento para la plataforma XAML.
Dan Taylor es un administrador de programas en el equipo de Microsoft .NET Framework. El año pasado, Taylor ha estado trabajando en el rendimiento de .NET Framework CLR para Windows 8 y núcleo CLR para Windows Phone 8.
Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Deon Brewis, Mike Hillberg, Dave Hiniker y Ivan Naranjo