{ "id": "82933", "key": "TIMOB-6242", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "12086", "description": "", "name": "Sprint 2011-46", "archived": true, "released": true, "releaseDate": "2011-11-21" }, { "id": "12580", "description": "Dual Runtime 1.8.0", "name": "Release 1.8.0.1", "archived": true, "released": true, "releaseDate": "2011-12-22" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2012-01-09T16:47:24.000+0000", "created": "2011-11-19T13:39:58.000+0000", "priority": { "name": "High", "id": "2" }, "labels": [ "branch-v8", "module_memory", "qe-testadded" ], "versions": [ { "id": "12580", "description": "Dual Runtime 1.8.0", "name": "Release 1.8.0.1", "archived": true, "released": true, "releaseDate": "2011-12-22" } ], "issuelinks": [], "assignee": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "updated": "2012-01-09T16:47:24.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10202", "name": "Android", "description": "Android Platform" } ], "description": "This happens only in V8, and only when the background image is based on something that is *not* a packaged asset. For example, it happens when the image is from a network url or from the device's sdcard.\r\n\r\nh3. Fail/Test Case\r\n\r\n* Run the app.js below on an emulator.\r\n* Open DDMS, select the running app, and turn on heap updates (the green cylinder button).\r\n* In DDMS, click on the \"Heap\" tab.\r\n* In the app, click the \"Show View\" and \"Remove View\" buttons back and forth many times, and watch how the \"Allocated\" value on the \"Heap\" tab continues to grow and grow.\r\n\r\nh3. Expected vs Actual Behavior\r\n\r\nEach time \"Remove View\" is clicked, the visible view (with the background image) is unloaded from the window via {{win.remove(view);}} and the {{view}} variable is set to null. It's *expected* that the view's memory footprint will reduce once it's removed, but what's *actually* happening is that memory usage continues to grow. (Specifically, we know that what's happening is that the bytes for the bitmap behind the backgroundImage of the view are not being released from memory.)\r\n\r\n{code:title=app.js}\r\nTitanium.UI.setBackgroundColor('#000');\r\nvar win = Titanium.UI.createWindow({ \r\n title:'Test',\r\n backgroundColor:'#000',\r\n\texitOnClose: true\r\n});\r\n\r\nvar view = null;\r\n\r\nvar btn1 = Ti.UI.createButton({\r\n\ttitle: \"Show View\",\r\n\tbottom: \"5dp\", height: \"40dp\", left: \"10dp\", width: \"150dp\"\r\n});\r\nbtn1.addEventListener(\"click\", function() {\r\n\tbtn1.enabled = false;\r\n\tview = Ti.UI.createView({\r\n\t\tbackgroundImage: \"http://www.appcelerator.com.s3.amazonaws.com/blog/images/frontpage/survey_hero_11112011.png\",\r\n\t\ttop: \"50dp\", left: \"50dp\", bottom: \"50dp\", right: \"50dp\"\r\n\t});\r\n\twin.add(view);\r\n\tbtn2.enabled = true;\r\n});\r\n\r\nvar btn2 = Ti.UI.createButton({\r\n\ttitle: \"Remove View\",\r\n\tbottom: \"5dp\", height: \"40dp\", left: \"165dp\", width: \"150dp\",\r\n\tenabled: false\r\n});\r\nbtn2.addEventListener(\"click\", function() {\r\n\tbtn2.enabled = false;\r\n\twin.remove(view);\r\n\tview = null;\r\n\tbtn1.enabled = true;\r\n});\r\n\r\nwin.add(btn1);\r\nwin.add(btn2);\r\n\r\nwin.open();\r\n{code}", "attachment": [], "flagged": false, "summary": "Android: V8 Memory Leak: views which contain backgroundImages not based packaged image assets", "creator": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "subtasks": [], "reporter": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "environment": "See in Titanium 1.8.0.1 with 654677d98 in an application using V8 runtime (does not happen with Rhino). Any Android device/emulator.", "comment": { "comments": [ { "id": "173439", "author": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "body": "When a packaged asset is used as the source of a background, the end result is that {{nativeDecodeAsset}} (in BitmapFactory.cpp) is called:\r\n\r\n{code:title=BitmapFactory.cpp}\r\nstatic jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,\r\n jint native_asset, // Asset\r\n jobject padding, // Rect\r\n jobject options) { // BitmapFactory$Options\r\n SkStream* stream;\r\n Asset* asset = reinterpret_cast(native_asset);\r\n // assets can always be rebuilt, so force this\r\n bool forcePurgeable = true;\r\n\r\n if (forcePurgeable || optionsPurgeable(env, options)) {\r\n // if we could \"ref/reopen\" the asset, we may not need to copy it here\r\n // and we could assume optionsShareable, since assets are always RO\r\n stream = copyAssetToStream(asset);\r\n if (NULL == stream) {\r\n return NULL;\r\n }\r\n } else {\r\n // since we know we'll be done with the asset when we return, we can\r\n // just use a simple wrapper\r\n stream = new AssetStreamAdaptor(asset);\r\n }\r\n SkAutoUnref aur(stream);\r\n return doDecode(env, stream, padding, options, true, forcePurgeable);\r\n}\r\n{code}\r\n\r\nIn that case the bitmap is always forced to be purgeable, no matter the options passed in.\r\n\r\nIn other cases (sdcard, network url), {{nativeDecodeStream}} is called:\r\n\r\n{code:title=BitmapFactory.cpp}\r\nstatic jobject nativeDecodeStream(JNIEnv* env, jobject clazz,\r\n jobject is, // InputStream\r\n jbyteArray storage, // byte[]\r\n jobject padding,\r\n jobject options) { // BitmapFactory$Options\r\n jobject bitmap = NULL;\r\n SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage);\r\n\r\n if (stream) {\r\n // for now we don't allow purgeable with java inputstreams\r\n bitmap = doDecode(env, stream, padding, options, false);\r\n stream->unref();\r\n }\r\n return bitmap;\r\n}\r\n{code}\r\n\r\nIn that case, purgeability is absolutely disallowed. That's the {{false}} value you see up there that is passed to {{doDecode}} for the {{allowPurgeable}} parameter.\r\n\r\nIf you look at {{doDecode}} (too long to paste here), you'll see purgeability makes a lot of differences. I don't really understand it all and haven't looked deeper, but for example here's one snippet where it appears that if it's not purgeable then the allocator is specifically set to a \"java allocator\":\r\n\r\n{code:title=snippet.cpp}\r\nif (!isPurgeable) {\r\n\tdecoder->setAllocator(&javaAllocator);\r\n}\r\n{code}\r\n\r\nWhat I definitely don't fully understand is why it makes a difference, Rhino vs. V8. The most striking difference to me is that when I view a V8 app in MAT, I see that our view proxies are all shown to be \"[Native Stack|https://skitch.com/billdawson/gkdqk/memory-analysis-var-folders-9o-9o2efpbafear0jpshi374-ti-tmp-android3582123121634602581.hprof-eclipse-users-bill-projects-eclipse-workspace]\", which makes it a \"GC root\". In Rhino, the view proxies are not GC roots, and their paths to GC roots lead to \"androidy\" things like view roots and handlers. I'd imagine this difference has something to do with it.\r\n", "updateAuthor": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "created": "2011-11-20T14:08:25.000+0000", "updated": "2011-11-20T14:09:13.000+0000" }, { "id": "173443", "author": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "body": "As additional test for the fix, we should be sure that we are still able to remove and re-add views successfully.\r\n\r\nTo test that, run the app.js below. You should be able to remove/re-add in succession successfully and always get the blue-red-green embedding of views. Also, the \"Click Me\" button inside the green view should always show an alert of \"Thanks\".\r\n\r\n{code:title=app.js}\r\nTitanium.UI.setBackgroundColor('#000');\r\nvar win = Titanium.UI.createWindow({ \r\n title:'Test',\r\n backgroundColor:'#000',\r\n\texitOnClose: true\r\n});\r\n\r\nvar v1, v2, v3, btn;\r\n\r\nvar viewOptions = {left: \"25%\", right: \"25%\", top: \"25%\", bottom: \"25%\"};\r\n\r\nwin.add(v1 = Ti.UI.createView({bottom: \"50dp\", left: 0, right: 0, top: 0}));\r\nv1.backgroundColor = \"blue\";\r\n\r\nv1.add(v2 = Ti.UI.createView(viewOptions));\r\nv2.backgroundColor = \"red\";\r\n\r\nv2.add(v3 = Ti.UI.createView(viewOptions));\r\nv3.backgroundColor = \"green\";\r\n\r\nv3.add(btn = Ti.UI.createButton({title: \"Click me\"}));\r\nbtn.addEventListener(\"click\", function() { alert(\"Thanks\"); });\r\n\r\n\r\nvar btn1 = Ti.UI.createButton({\r\n\ttitle: \"Remove\",\r\n\tbottom: \"5dp\", height: \"40dp\", left: \"10dp\", width: \"150dp\"\r\n});\r\nbtn1.addEventListener(\"click\", function() {\r\n\tbtn1.enabled = false;\r\n\twin.remove(v1);\r\n\tbtn2.enabled = true;\r\n});\r\n\r\nvar btn2 = Ti.UI.createButton({\r\n\ttitle: \"Re-add\",\r\n\tbottom: \"5dp\", height: \"40dp\", left: \"165dp\", width: \"150dp\",\r\n\tenabled: false\r\n});\r\nbtn2.addEventListener(\"click\", function() {\r\n\tbtn2.enabled = false;\r\n\twin.add(v1);\r\n\tbtn1.enabled = true;\r\n});\r\n\r\nwin.add(btn1);\r\nwin.add(btn2);\r\n\r\nwin.open();\r\n{code}", "updateAuthor": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "created": "2011-11-20T15:14:33.000+0000", "updated": "2011-11-20T15:14:33.000+0000" }, { "id": "173445", "author": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "body": "Pull request ready:\r\n\r\nhttps://github.com/appcelerator/titanium_mobile/pull/743\r\n\r\nThough this solution works and is probably good practice in any case, it doesn't address why there is this difference (see earlier comment) between V8 and Rhino.", "updateAuthor": { "name": "billdawson", "key": "billdawson", "displayName": "Bill Dawson", "active": true, "timeZone": "Europe/Berlin" }, "created": "2011-11-20T17:23:34.000+0000", "updated": "2011-11-20T17:23:47.000+0000" }, { "id": "175675", "author": { "name": "mpettiford", "key": "mpettiford", "displayName": "Michael Pettiford", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Tested on Ti Studio 1.0.7.201112080131

\r\nTi Mob SDK 1.8.0.1.v20111209102124 v8/rhino\r\n

OSX Lion
\r\nemulator 2.2\r\n\r\nTested both test cases and verified that the expected behavior is shown ", "updateAuthor": { "name": "mpettiford", "key": "mpettiford", "displayName": "Michael Pettiford", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2011-12-10T11:48:53.000+0000", "updated": "2011-12-10T11:48:53.000+0000" }, { "id": "178593", "author": { "name": "mpettiford", "key": "mpettiford", "displayName": "Michael Pettiford", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Reopening/closing to add/remove labels", "updateAuthor": { "name": "mpettiford", "key": "mpettiford", "displayName": "Michael Pettiford", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2012-01-09T16:46:59.000+0000", "updated": "2012-01-09T16:46:59.000+0000" } ], "maxResults": 5, "total": 5, "startAt": 0 } } }