从资产文件夹加载大约200MB文件时出现OutOfMemoryError错误

3

当我运行应用程序并想要加载时,Android无法分配足够的内存

java.lang.OutOfMemoryError: Failed to allocate a 202728962 byte allocation with 14422192 free bytes and 171MB until OOM

此前我曾在这里描述过一个错误:https://github.com/tensorflow/tensorflow/issues/9343 我猜测我有足够的自由内存(通常有1.0-1.5 GB可用): enter image description here 那么这只是Java Android的限制吗?我能否通过Java解决它?或者只可能使用NDK?
完整的错误代码:
04-21 09:40:52.731 31597-31597/org.tensorflow.demo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.tensorflow.demo, PID: 31597
    java.lang.OutOfMemoryError: Failed to allocate a 202728962 byte allocation with 14422192 free bytes and 171MB until OOM
        at org.tensorflow.contrib.android.TensorFlowInferenceInterface.loadGraph(TensorFlowInferenceInterface.java:377)
        at org.tensorflow.contrib.android.TensorFlowInferenceInterface.<init>(TensorFlowInferenceInterface.java:96)
        at org.tensorflow.demo.TensorFlowYoloDetector.create(TensorFlowYoloDetector.java:111)
        at org.tensorflow.demo.DetectorActivity.onPreviewSizeChosen(DetectorActivity.java:131)
        at org.tensorflow.demo.CameraActivity$1.onPreviewSizeChosen(CameraActivity.java:159)
        at org.tensorflow.demo.CameraConnectionFragment.setUpCameraOutputs(CameraConnectionFragment.java:421)
        at org.tensorflow.demo.CameraConnectionFragment.openCamera(CameraConnectionFragment.java:428)
        at org.tensorflow.demo.CameraConnectionFragment.access$000(CameraConnectionFragment.java:64)
        at org.tensorflow.demo.CameraConnectionFragment$1.onSurfaceTextureAvailable(CameraConnectionFragment.java:95)
        at android.view.TextureView.getHardwareLayer(TextureView.java:368)
        at android.view.View.updateDisplayListIfDirty(View.java:15175)
        at android.view.View.draw(View.java:15971)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3610)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3400)
        at android.view.View.updateDisplayListIfDirty(View.java:15193)
        at android.view.View.draw(View.java:15971)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3610)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3400)
        at android.view.View.draw(View.java:16204)
        at android.view.View.updateDisplayListIfDirty(View.java:15198)
        at android.view.View.draw(View.java:15971)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3610)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3400)
        at android.view.View.updateDisplayListIfDirty(View.java:15193)
        at android.view.View.draw(View.java:15971)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3610)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3400)
        at android.view.View.updateDisplayListIfDirty(View.java:15193)
        at android.view.View.draw(View.java:15971)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3610)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3400)
        at android.view.View.draw(View.java:16204)
        at com.android.internal.policy.PhoneWindow$DecorView.draw(PhoneWindow.java:2690)
        at android.view.View.updateDisplayListIfDirty(View.java:15198)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:282)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:288)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:323)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:2642)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2461)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2094)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1134)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6045)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:860)
        at android.view.Choreographer.doCallbacks(Choreographer.java:672)
        at android.view.Choreographer.doFrame(Choreographer.java:608)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:846)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5441)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
04-21 09:40:52.740 1501-2367/? E/ActivityManager: Invalid thumbnail dimensions: 0x0

TensorFlowInferenceInterface 类的 loadGraph 方法:

private void loadGraph(InputStream var1, Graph var2) throws IOException {
        long var3 = System.currentTimeMillis();
        Trace.beginSection("initializeTensorFlow");
        Trace.beginSection("readGraphDef");
        byte[] var5 = new byte[var1.available()];
        int var6 = var1.read(var5);
        if(var6 != var5.length) {
            throw new IOException("read error: read only " + var6 + " of the graph, expected to read " + var5.length);
        } else {
            Trace.endSection();
            Trace.beginSection("importGraphDef");

            try {
                var2.importGraphDef(var5);
            } catch (IllegalArgumentException var9) {
                throw new IOException("Not a valid TensorFlow Graph serialization: " + var9.getMessage());
            }

            Trace.endSection();
            Trace.endSection();
            long var7 = System.currentTimeMillis();
            Log.i("TensorFlowInferenceInterface", "Model load took " + (var7 - var3) + "ms, TensorFlow version: " + TensorFlow.version());
        }
    }

附带演示应用程序链接:github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android

另外,我被要求尝试使用本地代码路径来加载文件:graphsgithub.com/tensorflow/tensorflow/issues/9343#issuecomment-295959477 但是我并不精通C++ / NDK(我只是不使用它) 因此有一个可用于加载文件的.cc文件。 如果有人能够帮助我将其集成到TensorFlow示例Android项目中,我将不胜感激。 但是我仍然希望有一种无须NDK的解决方案,因为对于2016-2017年的Android设备而言,200MB真的是这样一个巨大的文件吗?


这可能会对你有所帮助 https://dev59.com/mV8e5IYBdhLWcg3wd6M6 - Amjad Omari
1个回答

1
我想我有足够的空闲内存(通常有1.0-1.5 GB可用)。
这是针对设备的。而不是针对您的应用程序
这是Android运行时限制。您有一个堆限制,您的堆可能被分段。
可能不能。您可以使用android:largeHeap请求比平均堆大的堆,但是:
  • 在低端设备上(例如Android One),它可能不会比现在更大
  • 大堆可能不超过200MB
  • 即使大堆超过200MB,您也可能没有一个单一的连续的200MB的自由堆空间块
  • 如果您正在消耗大量内存,则进程将在移至后台时迅速终止
只能通过NDK实现吗?
我不知道TensorFlow是否与NDK兼容,因为我没有使用过TensorFlow。您可以通过NDK分配系统RAM。能否分配200MB的系统RAM取决于设备上的系统RAM数量。
因为在Android 2016-2017设备中,以单个连续内存块的形式存在的200MB内存是非常大的尺寸。设备的年龄与其可用的系统RAM几乎没有关系。今天有许多设备安装了1GB或更少的系统RAM,更不用说在运行操作系统进程后还剩下多少了。例如,Android One设备可能配备512MB的系统RAM。

1
在最后一点上,移动设备拥有的RAM内存越多,电池就会消耗得越快。诚然,新一代的内存使用更少的功率(例如:https://www.gizmodo.com.au/2013/12/samsungs-new-chips-could-put-4gb-of-ram-into-every-phone/),而且电池也在不断改进。然而,仍然存在明显的权衡。 - Stephen C
@StephenC 使用神经网络的应用程序可能最适合在人们驾驶汽车时进行一些驾驶辅助,这种情况下我们必须在汽车和手机之间使用一些充电连接,因此我不认为这是一个问题。 - user924
@StephenC,这并不意味着在加载权重(200MB)后,应用程序将始终使用200MB。当我在我的应用程序中加载了Tiny YOLO(70MB)时,它仅在稍后使用20-40MB(整个应用程序)。这只是初始分配和内存使用,用于启动应用程序时加载文件。 - user924
1
你在这里做了很多假设,以争辩关于内存与功率的一般观点不适用。对于Tensorflow的某些应用程序,你的假设可能是有道理的... - Stephen C
1
你提到内存仅用于启动可能并不相关。对于 OOME(OutOfMemoryError),最重要的是最大使用量。最后,可能有优化方式来减少内存利用,但这已超出了像你这样的“如何解决 OOME”问题的范围。尝试在 Google 上搜索“tensorflow 内存优化”。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接