内存泄漏在Java中,但不是在Kotlin(相同的代码库)…为什么?

我在下面的活动中有一段简单的代码

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); valueAnimator.setRepeatCount(ValueAnimator.INFINITE); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { } }); valueAnimator.start(); } } 

如果活动终止,将会有内存泄漏(经泄漏金丝雀certificate)。

但是,当我把这个代码转换成相同的Kotlin代码(使用shift-alt-command-k)时,它如下

 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f) valueAnimator.repeatCount = ValueAnimator.INFINITE valueAnimator.addUpdateListener { } valueAnimator.start() } } 

内存泄漏不再发生。 为什么? 是因为匿名类对象转换为Lambda?

这两个版本之间的区别非常简单。

AnimatorUpdateListener Java版本包含对外部类的隐式引用(在您的情况下是MainActivity)。 所以,如果animation不再需要的时候animation继续运行,那么监听者就保持对活动的引用,防止它被垃圾收集。

Kotlin试图在这里变得更加聪明。 它看到传递给ValueAnimator的lambda没有引用来自外部范围的任何对象(即MainActivity ),所以它创建了AnimatorUpdateListener一个实例 ,当你重新开始animation的时候它将被重用。 而这个实例没有任何对外部作用域的隐式引用。

注意:如果将外部范围的某个对象的引用添加到lambda中,Kotlin会在每次animation开始时生成创建更新侦听器的新实例的代码,并且这些实例将隐含隐式对MainActivity引用(为了访问您决定在您的lambda中使用的对象所必需的)。

另一方面说明:强烈建议阅读“Kotlin in Action”一书,因为它包含了大量关于Kotlin的有用信息,我的解释是Kotlin编译器如何选择是否将对外部范围的隐式引用SAM转换后创建的对象来自本书。

1.检查实际情况

我想你会发现“显示Kotlin字节码”视图非常有助于看到究竟是怎么回事。 看到这里的InteliJ捷径 。 (会为你做这个,但没有你的应用程序更大的背景下很难)

2. JVM的相似之处,但Kotlin明显的差异

但是由于Kotlin和Java运行在同一个JVM上(所以和Java一样使用相同的垃圾回收器),你应该期待一个类似的安全的运行环境。 这就是说,当涉及到Lamdas和明确的引用,当他们转换。 它可以在不同的情况下有所不同:

就像在Java中一样,Kotlin中发生的情况在不同情况下会有所不同。

  • 如果lambda传递给一个内联函数并且没有标记为noinline,那么整个事情就会消失,没有额外的类
    或者创建对象。
  • 如果lambda没有捕获,那么它将作为一个单例类被释放,其实例被一次又一次地重用(一个类+一个对象分配)。
  • 如果lambda捕获,那么每次使用lambda时都会创建一个新的对象。

资料来源: http : //openjdk.java.net/jeps/8158765

3.总结,并进一步阅读

这个答案应该解释你看到什么,不能更好地解释它自己: https : //stackoverflow.com/a/42272484/979052不同的问题,我知道,但它背后的理论是相同的 – 希望帮助

正如你已经建议的那样, addUpdateListener的参数在两个版本中都是不同的。

我们来看一个例子。 我用一个抽象方法foo创建了一个类JavaAbstract

 public interface JavaInterface { void foo(); } 

这在JavaInterfaceClient

 public class JavaInterfaceClient { public void useInterfaceInstance(JavaAbstract inst){ inst.foo(); } } 

我们来看看如何从Kotlin调用useInterfaceInstance

首先 ,使用一个简单的lambda( SAM转换 ):

 JavaInterfaceClient().useInterfaceInstance {} 

在Java中表示的结果字节码:

 (new JavaInterfaceClient()).useInterfaceInstance((JavaInterface)null.INSTANCE); 

正如你所看到的,非常简单,没有对象实例化。

其次 ,用一个匿名实例:

 JavaInterfaceClient().useInterfaceInstance(object : JavaInterface { override fun foo() { } }) 

在Java中表示的结果字节码:

 (new JavaInterfaceClient()).useInterfaceInstance((JavaInterface)(new JavaInterface() { public void foo() { } })); 

在这里,我们可以观察到新的对象实例化,这从SAM转换/ lambda方法推迟。 你应该在你的代码中尝试第二个例子。