Xamarin Android 垃圾回收算法 [英] Xamarin Android garbage collection algorithm

查看:35
本文介绍了Xamarin Android 垃圾回收算法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读有关通过

根对象:

  • 静态字段/属性指向的对象
  • 每个托管线程的堆栈上的对象
  • 已传递到本机 API 的对象

基本上,您必须处理两个托管 GC.我们将它们称为 Xamarin GC 和 Android GC 以供参考.

Xamarin.Android 具有 peer objects,用于引用 Android JVM 中已知的本机 Java 对象.他们实现了一个核心接口:

命名空间 Android.Runtime{公共接口 IJavaObject : IDisposable{//JNI 对它所包装的 Java 对象的引用.//也称为指向 JVM 对象的指针公共 IntPtr 句柄 { 获取;放;}...}}

每当我们有一个继承了 IJavaObject 的对象时,它都会通过上面的 JNI 句柄保持一个强引用,以确保只要托管对象处于活动状态,它就会保持活动状态.

这样想:

IJavaObject -> IntPtr Handle -> Java 对象

在 GC 术语中,它将表示为:

由Xamarin GC分配和收集 -> GC Root -> 由Android GC分配和收集

然后我们在 Xamarin.Android 中有一个 GC 进程:

当 GC 运行时,您可以看到它将用弱引用替换强 JNI 句柄,然后调用将收集我们的 Java 对象的 Android GC.因此,会扫描 peers 以查找任何关系,以确保它们在 JVM 中被镜像.这可以防止过早收集这些对象.

一旦发生这种情况,我们就会运行 Android GC,当它完成时,它将遍历对等对象并检查弱引用.

  • 如果一个对象消失了,我们会在 C# 端收集它
  • 如果对象仍然存在,那么我们将弱引用改回强 JNI 句柄

因此,每次在 peer 对象上运行 GC 时,都需要检查和更新此图.这就是为什么这些包装器类型的对象要慢得多的原因,因为必须从对等对象开始扫描整个对象图.

因此,当我们的 peer 对象使用重要的对象图时,我们可以通过将引用的存储移到 peer 类之外来帮助 GC 过程.这通常是通过 rooting 我们独立于对等点的引用来完成的.并且由于它没有存储为字段,因此 GC 不会尝试在对象图上进行关系遍历.

如前所述,在您注意到长时间的 GC 之前,这不是一个需要担心的大问题.然后,您可以将其用作解决方案.

图片来源:Xamarin 大学(https://www.xamarin.com/university)

I am reading the Xamarin.Android garbage collection docs about helping the GC perform better by reducing referenced instances.

The section begins by saying:

Whenever an instance of a Java.Lang.Object type or subclass is scanned during the GC, the entire object graph that the instance refers to must also be scanned. The object graph is the set of object instances that the "root instance" refers to, plus everything referenced by what the root instance refers to, recursively.

...which I understand.

It then goes to show a custom class inheriting from the standard Activity class. This custom activity class has a field that is a list of strings which is initialized in the constructor to have 10,000 strings. This is said to be bad because all 10,000 instances will have to be scanned for reachability during GC. That I also understand.

The part that I am not clear on, is the recommended fix: it says the List<string> field should be moved to another class that doesn't inherit from Java.Lang.Object and then an instance of that class should be referenced from the activity just like the list was being referenced before.

My question: how does pushing a field deeper into the object graph help the GC when the total number of instances is still 10,000 and the opening paragraph says they will be scanned eventually because the process is recursive?

As a side note, I am also reading up (here) on the SGen GC used by Mono on Android and the object graph traversal process is described as being breadth-first starting with the GC roots. This explains how a 10,000 item list will cause a longer GC pause as each item is checked, but still doesn't explain how moving that list deeper into the graph will help because the GC will eventually scan it as it goes deeper into the graph.

解决方案

I'll try to explain this the best I can, and I'm nowhere near an expert here so anyone who wants to chime in, please do so.

When we are referring to doing a peer walk, we are locating any roots and traversing the live reference graph to see what is reachable and what is not:

Root Objects:

  • Objects pointed at by static fields / properties
  • Objects on the stack of each managed thread
  • Objects that have been passed into native APIs

Basically you then have to deal with two managed GCs. We'll call them the Xamarin GC and the Android GC for reference.

Xamarin.Android has peer objects which are used to reference the native Java objects known in the Android JVM. They implement a core interface:

namespace Android.Runtime
{
    public interface IJavaObject : IDisposable
    {
        // JNI reference to the Java object it is wrapping. 
        // Also known as a pointer to the JVM object
        public IntPtr Handle { get; set; }
        ...
    }
}

Whenever we have an object with IJavaObject inherited, it will keep a strong reference via that JNI handle above to ensure it is kept alive as long as the managed object is alive.

Think of it this way:

IJavaObject -> IntPtr Handle -> Java Object

In GC terms, it would be represented as the following:

Allocated and collected by Xamarin GC -> GC Root -> Allocated and collected by Android GC

We then have a GC process in Xamarin.Android:

When the GC runs, you can see that it will replace a strong JNI handle with a weak reference and then invoke the Android GC which will collect our Java object. Because of this, the peers are scanned for any relationships to ensure that they are mirrored in the JVM. This keeps these objects from being collected prematurely.

Once this happens, we run the Android GC and when it's finished it will walk through the peer objects and check the weak references.

  • If an object is gone, we collect it on the C# side
  • If an object still exists, then we change the weak reference back to a strong JNI handle

Thus this graph needs to be checked and updated each time a GC runs on peer objects. That's why it's much slower for these wrapper type objects because the entire object graph has to be scanned starting at the peer object.

So when there are significant object graphs that our peer object uses, we can help out the GC process by moving the storage of the references outside the peer class. This is usually done by rooting our reference independent of the peer. And since it's not stored as a field, the GC will not try to do a relationship walk on the object graph.

As noted earlier, this isn't a huge issue to worry about until you notice long GCs. You can then use this as a solution.

Image Credit: Xamarin University(https://www.xamarin.com/university)

这篇关于Xamarin Android 垃圾回收算法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆