在 React Native JavaScript 应用中,如果我创建一个临时变量而不是直接返回一个值,为什么 Android GC 行为会改变?

ndb*_*ent 5 javascript android garbage-collection react-native

我觉得 Android GC、JavaScriptCore 或 Genymotion 中的某个地方可能存在错误。我正在 Android 上测试 React Native 应用程序,所以我的所有代码都是用 JavaScript(而不是 Java)编写的。我试图找出我在以下 Android 模拟器上重现的崩溃:

  • 三星 Galaxy S6 - 6.0.0 - API 23
  • 谷歌 Nexus 5X - 7.1.0 - API 25

我注意到我的应用程序在使用大约 5 分钟后总是崩溃。使用 查看日志后adb logcat,我注意到它总是在 GC 运行后立即崩溃。它只在 Android 上崩溃,而在 iOS 上从未发生过崩溃。它也只有在“远程调试 JS”关闭时才会发生,我才意识到原因。这是因为当启用“远程调试 JS”时,所有 JS 都在我的笔记本电脑上的 Chrome 中运行,在 V8 引擎中。(这使得调试非常困难!)

我正在使用reselect库,但我使用了一个带有可配置“cacheSize”的分支来存储多个结果,而不是仅仅缓存一个结果。你可以在这里看到我的新defaultMemoize功能。我的第一个想法是我在这里做错了什么,但我没有使用 aWeakMap或类似的东西。我只是将缓存的结果存储在一个普通数组中,并且我总是持有对选择器的引用,所以我认为 GC 不应该清除内存。

我将描述有关我的应用程序的更多详细信息。我有一个LookupTable类,它引用一个Matrix类。查找表预先计算了一些东西以加快查找速度。

我的查找表代码如下所示:

// @flow
import autobind from 'autobind-decorator'

export default class LookupTable {
  matrix: Matrix

  constructor(state) {
    this.generateLookupTable(state)
  }

  @autobind
  valueAt(x: number, y: number) {
    this.matrix.get(x, y)
  }

  @autobind
  generateLookupTable() {
    // generates the Matrix at this.matrix
  }
}
Run Code Online (Sandbox Code Playgroud)

我的原始选择器代码如下所示:

import Immutable from 'immutable'
import { createSelectorCreator, defaultMemoize } from 'reselect'

const createImmutableSelector = (cacheSize = 1, ...args) =>
  createSelectorCreator(defaultMemoize, Immutable.is, cacheSize)(...args)

export const lookupTableSelector = createImmutableSelector(3,
  firstSelector,
  secondSelector,
  thirdSelector,
  fourthSelector,
  (one, two, three, four) =>
    new LookupTable(one, two, three, four))
Run Code Online (Sandbox Code Playgroud)

如果您不熟悉reselector immutable-js,我只是在记住一些计算(类似于 lodash 的memoize),并且我正在使用它Immutable.is来检查参数相等性。

最后,我会像这样调用选择器:

const lookupTable = lookupTableSelector(state)
const value = lookupTable.valueAt(x, y)
Run Code Online (Sandbox Code Playgroud)

Android GC 运行后,valueAt导致崩溃,因为this.matrix未定义。有趣的是,该lookupTable.generateLookupTable函数也是未定义的。事实上,实例中的所有内容都是未定义的。这就像选择器只是返回一个LookupTable实例的外壳,所有的数据和方法都消失了。

所以这是我尝试解决的方法:

export const lookupTableSelectorWithoutGCFix = createImmutableSelector(3,
  firstSelector,
  secondSelector,
  thirdSelector,
  fourthSelector,
  (one, two, three, four) =>
    new LookupTable(one, two, three, four))


export const lookupTableSelector = (gameState: Map) => {
  const lookupTable = lookupTableSelectorWithoutGCFix(gameState)

  if (lookupTable.matrix == null) {
    console.warn('LookupTable selector returned an instance with an undefined matrix.' +
      'This might be a GC bug on Android. Will clear the cache and generate a new instance.')
    lookupTableSelectorWithoutGCFix.clearCache()
    return lookupTableSelectorWithoutGCFix(gameState)
  }

  return lookupTable
}
Run Code Online (Sandbox Code Playgroud)

现在奇怪的是,这段代码从未真正被调用过。我从未在 中看到控制台警告adb logcat,它也从未在模拟器中显示为黄色框。在此之前,每次 GC 运行后都会发生崩溃。但是在这次更改之后,我通过 5 次 GC 运行测试了该应用程序,一切都运行良好。所以无论我在这里做了什么,我想它改变了 Android GC 的行为,它不再为我的LookupTable实例清除内存。

你能看出为什么会发生这种情况吗?或者这可能是 Android GC 中的引用计数错误?