内存泄漏在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方法推迟。 你应该在你的代码中尝试第二个例子。
- 建设失败Kotlin kapt和房间
- 用Kotlin关闭/隐藏Android软键盘
- 在高阶函数中调用具有参数/ s的lambda
- Json给kotlin数据类
- 在Instant App中应用插件“kotlin-android”会导致“null不能转换为非空类型的com.android.build.gradleBasePlugin”
- genericstypes和多态性
- @Inject设置不注入属性
- 如何在WebView里添加“返回”功能在Fragment里面?
- 无法find方法’com.android.build.gradle.api.BaseVariant.getOutputs()Ljava / util / List;’