Skip to content

TroubleShooting โ€ Memory Leak

Taewan Park edited this page May 13, 2024 · 19 revisions

๋ฌธ์ œ ์ƒํ™ฉ

KakaoTalk_20231207_105337219.mp4

image

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ฆญ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

  1. ํ™ˆ ํ™”๋ฉด์—์„œ ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ํ™”๋ฉด์„ ์ด๋™ํ•  ๊ฒฝ์šฐ, Java Heap๊ณผ Native Heap์ด GC ์ฝœ ์ดํ›„์—๋„ ํฌ๊ธฐ๊ฐ€ ์ค„์ง€ ์•Š๊ณ  ๊ณ„์† ๋Š˜์–ด๋‚œ๋‹ค.
  2. ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์—์„œ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  3. ์›น๋ทฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋กœ์ง์—์„œ ์›น๋ทฐ๊ฐ€ ๋‹ซํž ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

ํ•ด๊ฒฐ ๊ณผ์ •

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  
  debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
}

๋ฉ˜ํ† ๋‹˜๊ณผ์˜ ๋Œ€ํ™”๋ฅผ ํ†ตํ•ด LeakCanary๋ฅผ ํ†ตํ•ด ์–ด๋””์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ์ถ”์ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฑ„ํƒํ–ˆ๋‹ค.

LeakCanary๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์•Œ๊ณ  Activity, Fragment, View, ViewModel์ด ํŒŒ๊ดด๋์„ ๋•Œ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜์—ฌ ObjectWatcher๋กœ ์ „๋‹ฌํ•˜๊ฒŒ ๋œ๋‹ค.

ํŒŒ๊ดด ํ›„ 5์ดˆ ์ด๋‚ด์— GC๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด retained ๋œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜์—ฌ ์ด๋ฅผ Logcat์— ๊ธฐ๋กํ•œ๋‹ค.

retained ๊ฐ์ฒด์ˆ˜๊ฐ€ ์ž„๊ณ„๊ฐ’์„ ๋„˜์–ด๊ฐ€๋ฉด LeakCanary๋Š” Java ํž™ ๋ฉ”๋ชจ๋ฆฌ ์˜์—ญ์„ ์•ˆ๋“œ๋กœ์ด๋“œ ํŒŒ์ผ ์‹œ์Šคํ…œ์— .hprof ํŒŒ์ผ๋กœ ๋คํ”„ํ•œ๋‹ค.

ํž™์„ ๋คํ”„ํ•˜๋ฉด ์งง์€ ์‹œ๊ฐ„๋™์•ˆ ์•ฑ์ด ์ •์ง€๋˜๋ฉฐ, ์ดํ›„ ํž™ ๋ฉ”๋ชจ๋ฆฌ ๋ถ„์„์„ ํ†ตํ•ด retained ๊ฐ์ฒด๋“ค์„ ๊ฒ€์‚ฌํ•˜๊ณ  ๋ถ„์„ํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค.

๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด

์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฝํ—˜ํ•˜๋Š” ์ฒซ ๋ˆ„์ˆ˜ ๋ฐœ์ƒ ํ™”๋ฉด์€ ๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์ด์—ˆ๋‹ค.

๋กœ๊ทธ์ธ ํ™”๋ฉด๊ณผ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์—์„œ๋Š” API ์‘๋‹ต์‹œ๊ฐ„ ๋™์•ˆ ์›ํ˜•์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๋กœ๋”ฉ ์•„์ด์ฝ˜์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ,

ํ•ด๋‹น ์•„์ด์ฝ˜์„ ๊ทธ๋ฆฌ๋Š” ๋ถ€๋ถ„์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

private lateinit var circularProgressIndicator: IndeterminateDrawable<CircularProgressIndicatorSpec>
...
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    with(binding) {
        viewModel = loginViewModel
    }
    circularProgressIndicator = getCircularProgressIndicatorDrawable(this@LoginActivity)
    initListener()
    collectEvent()
    setContentView(binding.root)
}
...
override fun onDestroy() {
    super.onDestroy()
    circularProgressIndicator.stop()
}

๋ˆ„์ˆ˜ ๋ฐœ์ƒ ์›์ธ์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ข…๋ฃŒ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํ•ด๋‹น ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋“ค์–ด์žˆ๋Š” ์•„์ด์ฝ˜์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•œ ํ›„ null๋กœ ์ฐธ์กฐ๋ฅผ ์—†์•ด์ง€๋งŒ,

์‹ค์ œ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋‹ด์€ ๊ฐ์ฒด๋Š” ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ๊ณ„์† ๋Œ์•„๊ฐ€๊ฒŒ ๋œ๋‹ค.

์ด ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์‹คํ–‰๋˜๊ธฐ ์œ„ํ•ด context๋ฅผ ์ฐธ์กฐํ•˜๋Š”๋ฐ ์ด ๊ณผ์ •์—์„œ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด์—ˆ๋‹ค.

circularProgressIndicator์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ onDestory์—์„œ ๋ฉˆ์ถ”๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

์œ„ ์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝ ํ›„ ๋‹ค์‹œ ํ…Œ์ŠคํŠธํ•œ ๊ฒฐ๊ณผ ๋” ์ด์ƒ ๋กœ๊ทธ์ธ ํ™”๋ฉด๊ณผ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์—์„œ๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜๋‹ค.

์ƒํ’ˆ ์ถ”๊ฐ€ ๋„์›€๋ง ํ™”๋ฉด

image

๋‹ค์Œ์œผ๋กœ ๋ฐœ์ƒํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋Š” ์ƒํ’ˆ ์ถ”๊ฐ€ ๋„์›€๋ง ํ™”๋ฉด์ด๋‹ค.

ํ•ด๋‹น Fragment๋Š” WebView๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = ActivityLinkHelperWebViewBinding.inflate(layoutInflater)
    setContentView(binding.root)

    webView = WebView(this)
    webView.loadUrl("https://info-kr.priceguard.app/")
    binding.wbLinkHelper.addView(webView)
}

override fun onDestroy() {
    super.onDestroy()
    binding.wbLinkHelper.removeAllViews()
    webView.clearHistory()
    webView.clearCache(true)
    webView.loadUrl("about:blank")
    webView.pauseTimers()
    webView.destroy()
}

Activity๊ฐ€ ์ œ๊ฑฐ๋  ๋•Œ ๋˜๋ฉด WebView๋Š” ๊ณ„์†ํ•ด์„œ Activity์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ ํ™œ๋™์ด ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๊ฑฐ๋˜์ง€ ์•Š๋Š”๋‹ค.

์ด๋ ‡๊ฒŒ ๋  ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์›น๋ทฐ๋ฅผ xml์—์„œ ๋งŒ๋“ค๋„๋ก ํ•˜์ง€ ์•Š๊ณ  ์ฝ”๋“œ์—์„œ ์ƒ์„ฑํ•˜์—ฌ ์ด๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค.

https://issuetracker.google.com/issues/273117792#comment4

์‹ค์ œ๋กœ ๋ˆ„์ˆ˜๋Š” ์–ด๋Š์ •๋„ ์žกํ˜”์ง€๋งŒ, ์—ฌ์ „ํžˆ LeakCanary๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๊ฒฝ๊ณ ํ•˜๊ณ  ์žˆ๋‹ค.

์ด์œ ๋ฅผ ์ฐพ๋Š” ๋„์ค‘ ํ˜„์žฌ ์‚ผ์„ฑ ๊ธฐ๊ธฐ ์ค‘ ์•ˆ๋“œ๋กœ์ด๋“œ 13๋ฒ„์ „์— OneUI 5๋ฒ„์ „์—์„œ LeakCanary ์‚ฌ์šฉ ์‹œ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ์ด์Šˆ๋ฅผ ๋ฐœ๊ฒฌํ•˜์˜€๋‹ค.

ํ˜„ ์ƒํ™ฉ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์ด๋ผ ์ƒ๊ฐํ–ˆ๊ณ , ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ”„๋กœํŒŒ์ผ๋ง์—์„œ ์‹ฌ๊ฐํ•œ ์ŠคํŒŒ์ดํฌ๋‚˜ ์ ์ง„์ ์ธ ํž™ ์˜์—ญ ์ฆ๊ฐ€๋Š” ์—†์—ˆ๋‹ค.

๋”ฐ๋ผ์„œ ํŒ€๊ณผ ์˜๋…ผ์„ ํ†ตํ•ด ํ•ด๋‹น ์ด์Šˆ์— ๋Œ€ํ•œ ํƒ์ƒ‰์€ ์—ฌ๊ธฐ์„œ ์ค‘๋‹จํ•˜์˜€๋‹ค.

์ƒํ’ˆ ๋ชฉ๋ก/์ธ๊ธฐ ์ƒํ’ˆ ํ™”๋ฉด

์ƒํ’ˆ ๋ชฉ๋ก ํ™”๋ฉด ํ”„๋ž˜๊ทธ๋จผํŠธ์—์„œ๋„ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

ํ•ด๋‹น ํ™”๋ฉด์—์„œ RecyclerView๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด adapter๋ฅผ ์„ค์ •ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ,

RecyclerView ์–ด๋Œ‘ํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋ฉด ์–‘๋ฐฉํ–ฅ ํ†ต์‹  ๋ฐฉ๋ฒ•์ด ์„ค์ •๋œ๋‹ค.

ํ•ด๋‹น ์—ฐ๊ฒฐ์„ ์ •๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฐ€๋น„์ง€ ์ˆ˜์ง‘๊ธฐ๊ฐ€ RecyclerView๋ฅผ ์ง€์šธ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

override fun onDestroyView() {
    super.onDestroyView()
    binding.rvRecommendedProduct.adapter = null
}

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์–ด๋Œ‘ํ„ฐ๋ฅผ ๋ชจ๋‘ onDestoryView์—์„œ null๋กœ ์ดˆ๊ธฐํ™”ํ–ˆ๋‹ค.

์ด์ œ ๋” ์ด์ƒ Fragment ๋ผ์ดํ”„์‚ฌ์ดํด ์ดํ›„ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

image

๋ฌธ์ œ ํ•ด๊ฒฐ ์ดํ›„ ๋” ์ด์ƒ Fragment๋กœ ์ธํ•œ ๋ˆ„์ˆ˜๋Š” ์—†์–ด์กŒ๋‹ค.

BottomNavigatorView์—์„œ Navigate ์ฝœ์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ

์–ด๋Œ‘ํ„ฐ์™€ ๋ฆฌ์Šค๋„ˆ ์ดˆ๊ธฐํ™”๋ฅผ ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ Java Heap 63MB, Native Heap 104.6MB์˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์ตœ์ข…์ ์œผ๋กœ ํ• ๋‹น๋˜์ง€๋งŒ,

์–ด๋Œ‘ํ„ฐ์™€ ๋ฆฌ์Šค๋„ˆ ์ดˆ๊ธฐํ™”๋ฅผ ํ•  ๊ฒฝ์šฐ Java Heap์€ 15 ~ 25MB, Native Heap 28 ~ 33MB ์‚ฌ์ด์—์„œ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์œ ์ง€๋œ๋‹ค.

++์ถ”๊ฐ€

Fragment์—์„œ Adapter๋ฅผ onViewCreated ํ• ๋•Œ ๋งˆ๋‹ค ์ƒ์„ฑํ•˜๊ณ ์žˆ๋„ค์š”. ์•„๊นŒ ๋ฉ”๋ชจ๋ฆฌ ๋ฆญ์€ ์žฌ์‚ฌ์šฉํ•˜๋Š” ํ˜•ํƒœ๋กœ adapter๋Š” 1ํšŒ ๋งŒ๋“ค๊ณ , ์ด๊ฑธ view์— ์ ์šฉ๋งŒ ํ–ˆ๋‹ค๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ฌธ์ œ๊ฐ€ ์กฐ๊ธˆ ๋” ๋นจ๋ฆฌ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ ์ƒ๊ฐ๋“œ๋„ค์š”. Fragment view lifecycle ์ฐธ๊ณ .(๊ตฌ๊ธ€ binding์„ nullable๋กœ ํ•˜๋Š” ์ด์œ ์™€ ๊ฐ™๊ธดํ•ฉ๋‹ˆ๋‹ค.)

๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜„์—…์ž์˜ ํ”ผ๋“œ๋ฐฑ์ด ์žˆ์—ˆ๋‹ค.

๋ง์”€ํ•˜์‹  ๋‚ด์šฉ๋Œ€๋กœ adapter๋ฅผ ์บ์‹ฑํ•˜์—ฌ ์‹คํ–‰ ์ค‘ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์œผ๋ฉด ํ›จ์”ฌ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๊ฐ€ ๋˜์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

์ถ”ํ›„ ๋ฆฌํŒฉํ† ๋ง ๊ณผ์ •์— ๋ฐ˜์˜ํ•ด์•ผ๊ฒ ๋‹ค.

์ฐธ๊ณ  ์ž๋ฃŒ

๊ธฐํš

Convention

์˜์‚ฌ ๊ฒฐ์ • ๊ธฐ๋ก

๋ฌธ์ œ ํ•ด๊ฒฐ ๊ธฐ๋ก

์†๋ฌธ๊ธฐ

์ตœ๋ณ‘์ต

๊ฐ•์€ํ˜ธ

๋ฐ•์Šน์ค€

๋ฐ•ํƒœ์™„

์Šคํ”„๋ฆฐํŠธ ๊ณ„ํš ํšŒ์˜

์ฃผ์ฐจ๋ณ„

Scrum

์ฃผ์ฐจ๋ณ„

Mentoring

์ฃผ์ฐจ๋ณ„

Retrospect

Team
Personal
Clone this wiki locally