Java에는 다음과 같은 특징이 있다.
  1. JVM 덕분에 Hardware에 종속적이지 않다.
  2. Oriented-Object-Programming
  3. GC 덕분에 메모리 alloc, dealloc 에서 자유롭다
  4. 굉장히 많은 Library


 이 중에서 특히 3번. GC에 대해서 포스팅을 작성해보려고 한다. 고급 Java개발자로 가기 위해서는 GC에 대해서 정확히 이해하고 있어야 하며, 필요에 따라서는 튜닝도 할 수 있어야 한다고 한다. 지금까지 GC가 하는 역할은 어렴풋이 알았지만 내부적인 프로세스에 대해서는 알고 있지 않았다.


 Java에서는 객체들이 Heap 영역에 할당된다. 그래서 이 Heap영역이 가득차면 Out of Memory 경고가 발생한다. 그렇기 때문에 GC가 동작하면 Heap영역에서 사용하지 않는 객체들을 제거해준다. 


우선, 전체적으로 큰 그림을 보자.


 기본적으로는 used(referenced) object와 unused(unreferenced) object를 구분해야 한다. 그래야만 어떤 객체를 해제할 지 알 수 있기 때문이다. 여기서 used, unused의 차이는 Application 내에서 Object를 가리키는 포인터가 존재하면 여전히 사용하는 것이고, 포인터가 하나도 없다면 사용하지 않는 것이다.

 아래 그림처럼 Marking 을 하고 나면 Unreferenced Objects 가 표시가 된다. 이 때는 전체 객체를 탐색해야 하기 때문에 어쩔 수 없이 긴 시간이 소요된다.

 다음 단계로는 Unreferenced Objects를 delete해주는 것이다. 여기서 특징이 메모리 해제만하고 끝나는 것이 아니라 Memory Allocator들이 비어있는 공간을 잡고 있다는 부분이다.

 이렇게하면 굳이 메모리 전부를 탐색하면서 비어있는 공간을 찾아서 할당하지 않고, Memory Allocator가 잡고 있는 영역에만 할당하면 된다. 그래서 언제든지 새로 allocation을 해야하는 경우 빠르게 찾을 수 있다.

 하지만 위의 그림의 단점은 객체마다 할당되는 메모리 크기 다르기 때문에 점점 단편화가 심해진다는 것이다. 그래서 비어있는 공간에 딱 맞는 객체가 할당되면 모르겠지만 크다면 할당할 수 없고, 작다면 공간이 남아 비효율적인 상황이 발생하게 된다.

 그래서 Compacting이라는 작업이 있다. 아래 그림처럼 할당되어 있는 오브젝트들이 맨 앞에부터 차례대로 재배치 되는 것이다. 그러면 Memory Allocator는 비어있는 공간에 만 앞에만 알고 있으면 된다.

요약하자면,

  1. Unreferenced Objects 를 찾아서 Marking한다.
  2. Delete한다.
  3. Compacting을 하여 사용 중인 객체를 앞에서부터 차례로 재배치 하여 빈 공간을 확보한다.

그런데 여기서 생각해 볼 문제가 있다.

 GC가 발생하면 Stop the world 이벤트가 발생한다. 이것은 현재 Application 내의 모든 쓰레드의 동작을 중지시키고 GC작업을 하는 것이다. 따라서 이 시간이 길어질 수록 Application의 성능은 나빠질 수 밖에 없다. 고급 단계에서 GC를 튜닝한다는 것은 Stop the world의 시간을 얼마나 단축시키느냐에 관련있다.


 그렇다면 위에 설명한 Marking에서는 전체 Object를 스캔해야 하기 때문에 어쩔 수 없이 발생하는 시간이 있다. 만약 Heap 전부가 찰 경우에 시작하면 늦으니 어느 정도 찼을 때 시작한다고 하더라도 그 시간은 제법 길 것이다. 그리고 Heap의 크기를 늘리면 늘릴 수록 그 시간은 더욱 길어질 것이다. 


 그래서 JVM의 성능 향상을 위해서 Heap의 영역을 Young, Old, Permanent 구분했다.


Young Generation : 객체가 처음 생성되면 이곳에 할당 된다. 여기서 발생하는 GC를 Minor Garbage Collection이라고 부른다. 이 영역에서는 Unreferenced Objects에 대한 스캔이 빠르게 일어난다. 살아남은 객체(Surviving Objects)들이 있다면 Aging시켜서 Old Generation으로 이동시킨다.


Old Generation : 이곳에는 오랫동안 살아남은 객체들이 존재하는 영역이다. 여기서 발생하는 GC를 Major Garbage Collection이라고 부른다. Major GC는 Minor GC보다 훨씬 느리다. 그렇기 때문에 이 영역을 최소화 하는 것이 좋다.


Permanent Generation : 이곳에는 JVM이 실행되기 위해 필요한 class와 method에 대한 metadata가 포함되어 있다. 이곳가지 GC를 하는 경우는 거의 없으나 여기 까지 하게 된다면 Full Garbage Collection이 된다. 이곳까지 GC를 해야하는 경우는 메모리 누수에 심각한 문제가 있다고 생각해도 좋다.


그렇다면 본격적으로 각 영역에서 GC가 어떻게 동작하는지 살펴보자.


1. 먼저 처음 생성 된 객체는 Young Generation에서 Eden영역에 할당된다.


2. Eden 영역이 가득차게 되면 GC가 동작하고, 여기서 살아남은 객체는 Survivor Space로 이동한다. Survivor Space는 2개가 있고, 이 중 S0 Survivor space로 이동한다.

3. 그러면 아래 그림과 같이 Eden영역에서 Unreferenced Objects들은 delete되고, 살아남은 객체들은 S0 Survivor space로 이동하게 된다.

4. 다음 Minor GC가 발생하면 여기서는 살아남은 객체가 S0 Survivor space로 가는 것이 아니라 S1 Survivor space로 이동하게 된다. S0영역에서 살아남은 객체는 Aging이 되어서 S1 Survivor space로 이동한다. 그러면 Eden과 S0 survivor space는 clean하게 된다.

5. 다음 Minor GC가 발생하면 위와 같은 프로세스가 반복된다. 다만, Survivor space가 교체된다. 즉, 위에서는 S0에서 S1으로 갔다면 이번에는 S1에서 살아남은 객체는 Aging되어서 S0로 이동된다. 그리고 Eden과 S1영역은 clean된다. 

 이렇게 GC가 발생할 때마다 S0 - S1이 서로 교체되면서 살아남은 객체를 Aging시킨다.

6. 위의 과정을 반복하다가 객체의 age가 특정 기준을 넘어서면(아래 그림에서는 8) 그 객체는 Old Generation으로 넘어간다. (Tenured와 Old Generation은 같은 말)

 아래 기준으로 보자면 8번의 GC를 하고도 살아남은 객체기 때문에 굉장히 오래 살아남은 객체라고 할 수 있다.

7. 위의 과정을 반복하면서 Old Generation에 객체가 쌓이게 되면 어느 순간 Major GC가 발생하게 된다.


이상으로 Garbage Collection의 전반적이 프로세스에 대해 살펴봤다.

큰 그림은 Marking - Deleting - Compacting이지만 이 과정을 좀 더 효율적으로 하기 위해 Heap영역을 Young, Old, Permanent로 나뉘었다. 각 영역에서 발생하는 GC는 marking과 deleting-compacting의 과정을 거친다.


 기억해야할 특징은 Aging이다. Minor GC가 발생하면서 살아남은 객체는 Aging되면서 일정 age에 도달하면 Old Generation으로 이동한다. 

 

 Major GC는 Minor GC보다 훨씬 큰 비용이 든다. 따라서 Applicatoin이 Minor GC로만 충분히 동작이 가능하다면 좋은 성능을 낼 수 있다는 얘기다. 그럴려면 코딩을 할 때 객체가 오래 살아남지 않도록 주의를 해야한다. 

 그러려면 객체를 전달할 때, 그 객체의 참조를 전달하기보다는 값을 전달해서 새로운 객체를 만들어서 관리하는 것이 Aging을 예방할 수 있는 방법이라고 할 수 있겠다. 이 부분에 대해서는 아직 경험이 부족하므로 나중에 Java Coding실력이 훨씬 성숙해진다면 그와 관련된 글도 포스팅 해보고 싶다.






도움이 될 만한 사이트 : 

* GC 과정 : http://d2.naver.com/helloworld/1329

* Java Reference와 GC : http://d2.naver.com/helloworld/329631

글과 사진 참조 :




Posted by 와이빈

Fragment

Android 2015.08.16 15:51

 Android는 Android 3.0(API level 11)에서 fragment를 소개했다. API level 11이 중요한 이유는 이 때가 Honeycomb인데, 이 시기부터 Tablet과 Mobile이 합쳐졌기 때문이다. 그래서 Android를 개발할 때 API 11 이하를 지원하느냐 안하느냐에 따라 사용해야하는 API도 많이 달라진다.


 위의 그림과 같이 Tablet의 경우 Mobile보다 훨씬 큰 화면을 가지고 있다. 만약 Fragment가 없다면 위의 경우를 대응하기 위해서는 Tablet용 view와 mobile용 view 2개. 총 3개의 view를 만들어야 한다. 하지만 Fragment를 사용한다면 Tablet에서는 Fragment 2개를 한 Activity 내에서 사용하고, Mobile에서는 각 화면에 한 Fragment를 사용해서 재사용을 할 수 있다. Fragment는 각 각의 디자인들을 모듈화 해서 재사용성을 높이기 위해 탄생한 기술인 것이다.


 재사용(reusable)이라는 단어가 Fragment에서는 중요하다. 즉, Fragment는 특정 Activity에 종속되지 않기 때문에 다른 Activity에서도 언제든 재사용할 수 있다. 그렇기 때문에 Fragment는 자신을 호출한 Activity를 host activity라는 용어를 사용해서 지칭한다. 그래서 getActivity()라는 method를 통해서 자신의 host activity를 알 수 있다. 

 











 

 Fragment는 각 각의 layout과 lifecycle을 가지고 있기 때문에 하나의 fragment를 여러 activity에 포함할 수 있다.  

 Fragment를 만들기 위해서는 Fragment Class를 상속 받으면 된다. 위의 Lifecycle callback method중에서 적어도 onCreate(), onCreateView(), onPause() 함수는 구현해야 한다.



 그런데 우리가 눈여겨 봐야할 transition code가 있다. 보면 Activity의 Lifecycle과 비슷해보인다. 그러면 굳이 Activity와 겹치는 Lifecycle은 기억할 필요가 없다. Acitivity를 공부하면서 충분히 외웠을 테니깐.


 onAttach(), onCreateView(), onAcitivityCreated(), onDestroyView(), onDetach() 가 Acitivity와는 구분 되는 Fragment만의 Lifecycle method이다.


 특히, onStop()에서 화살표가 위로 올라가던 Activity와 달리, Fragment에서는 onDestroyView()에서 화살표가 위로 올라가 있다. 이 말은 우리가 onStop()에서 구현해야할 부분은 이곳에서 구현해도 된다는 것이다. 마찬 가지로 onStart()에서 구현해야할 부분은 onCreateView()에서 구현해도 된다는 얘기다. 


'The fragment returns to the layout from the back stack'

 이 문구를 보면 Fragment는 back stack으로부터 return된다는 것인데, 그러면 Fragment는 기본적으로 Back Stack에 쌓인다는 말이다. 하지만 이상하게 Fragment를 Back Stack에 쌓으려면 addToBackStack(null); 이 method를 호출해야 쌓인다. 그러면 기본값은 안쌓인다는 말 같은데 이 그림을 보면 이상하다. 잘못 설명한 것일까?

 이에 대한 답은 Back Stack에 쌓을 필요가 없는 경우를 위해서 만든 method인데, 그러다보니 기본적으로 Fragment가 Back Stack에 안 쌓인다고 생각을 하는 경우가 많아서 저렇게 별도로 설정을 둬서 인식하라고 했다고 한다. 확 와닿지는 않지만 사실 Fragment도 Back Stack에 쌓는 것이 default라고 생각하고 저 그림을 보는 것이 이해하기는 쉽다.


Developer Android에서 Fragment에 대해 검색해보면, Activity의 Lifecycle과 비교한 그림이 있다. 이 그림은 절대 기억할 필요가 없다. 처음에 Acitivity와 같이 호출 되는 Fragment의 경우야 그 그림이 맞을 수 있는데, 보통 Fragment는 Acitivity는 살아 있는 채 Fragment가 바뀌면서 사용하는 경우가 대부분이다. 그럴 경우에는 그림에서 설명 하는 대로 짝이 안맞기 때문에 연계해서 생각하면 더 헷갈린다. 따라서 여기서도 그 그림은 첨부하지 않는다.


Fragment를 사용하는 방법은 2가지가 있는데

  • <fragment> 태그 사용
  • getFragmentManager() 사용
XML에 직접 넣어서 사용하는 방법은 Fragment를 잘 사용하는 방법이라고 할 수 없으므로, 이 방법은 굳이 기억하지 않아도 좋다. 대신, Runtime시에 동적으로 Fragment를 사용하는 아래 방법을 사용해서 Fragment를 사용하는 예제를 연습해 보는 것이 좋다.


// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();





<주의> 위에 설명했 듯이 Fragment는 API level 11부터 제안 된 개념이다. 그러다보니 그 아래 버전에는 Fragment를 위한 기능이 기본적으로는 없다. 그래서 Support Library를 사용해서 Fragment를 구현해야 한다. Activity도 FragmentActivity를 상속받아야 한다. 만약, minSdk가 11 이상이라면 굳이 Support Library를 이용하여 Fragment를 구현할 필요가 없다.







[참고] http://developer.android.com/intl/ko/training/basics/fragments/index.html

[사진, 글 출처http://developer.android.com/intl/ko/guide/components/fragments.html

'Android' 카테고리의 다른 글

Fragment  (0) 2015.08.16
Tasks and Back Stack  (0) 2015.08.08
Android에서 정말 중요한 Manifest  (0) 2015.08.06
Posted by 와이빈

Tasks and Back Stack

Android 2015.08.08 15:37

 처음에 안드로이드를 공부하다보면 Task, Stack의 개념이 굉장히 혼동된다.(물론 지금도)


 안드로이드 컨셉의 특징 중 다른 Application의 Activity를 사용한다는 점이 있다. (나는 갤러리 앱을 만들지도 않았는데, 갤러리 앱의 Activity를 사용할 수 있는 것처럼). 실제로는 다른 앱의 Activity를 사용하는데, 사용자는 마치 하나의 앱에서 동작이 되는 듯한 느낌을 받는다.

 

 Even though the activities may be from different applications, Android maintains this seamless user experience by keeping both activities in the same task.

 안드로이드가 2개의 다른 application의 activity를 같은 task에서 유지시키기에 사용자는 매끄럽게 동작되는 느낌을 받는다는 말 같다.


A task is a collection of activities that users interact with when performing a certain job.

activity의 모음을 task라고 하는 것을 알 수 있다. activity들은 back stack이라고 불리는 stack에 쌓임으로써, 우리는 Back Button을 눌려서 이전의 Activity로 돌아갈 수 있다.


앱을 시작하면 "main" Activity를 제일 먼저 실행하게 되는데, stack에 root activity로써 새로운 Task가 생성되게 된다. 


 가상 머신 또는 안드로이드 단말기를 연결하고 terminal 창에서 >adb shell dumpsys activity 라는 명령어를 입력하면 위와 같은 결과를 얻을 수 있다.


 기본적으로 Stack #0 에는 Task가 항상 존재하는데,  안드로이드 핸드폰을 실행하면 보게 되는 Home 화면이다.

 위의 사진은 가상머신에 기본으로 설치된 계산기 앱을 실행하고 명령어를 입력했을 때 결과이다. Stack #1이 생기고, 그곳에 Task id #21이 생겼다. 그리고 Hist #0 .Calculator 라는 Activity가 실행되고 있음을 알 수 있다.


 앱을 여러개 실행시키면 Stack #1에서 Task가 계속 추가되고, 한 앱에서 여러 Acitivity를 부르면 Task내에서 Hist가 추가되는 것을 확인할 수 있다.


아래 그림을 보면 보통 Back Stack에 Activity가 쌓이는 그림으로 표현한다. 그런데 위의 Shell을 확인해보니 Task가 쌓이고 그 아래에 Hist# 으로 Activity가 쌓이는 것을 확인할 수 있다. 그리고 Stack의 갯수는 2개가 보인다.

 실제로 Stack은 2개가 존재하면 Stack#0은 바뀌지 않는다. 그래서 우리가 흔히 말하는 Back Stack은 Stack#1이라고 보면 된다. Back Stack에는 Task단위로 Activity들이 쌓이게 된다. Task 단위마다는 break가 걸리기 때문에 아무리 Back Button을 누르더라도 Task에서 다른 Task로 넘어가지 않는다. 우리가 앱을 종료하더라도 다른 앱 화면으로 넘어가지 않는 이유가 이것 때문이다.




. Activity1에서 Activity2로 화면이 바뀌면 Activity1은 Stop 상태가 되면서 Activity2가 Back Stack에 Push된다. 

 Activity3에서 Back Button을 누르면 Activity 3은 Pop되고 destroy 된다. 그리고 Activity2는 다시 resume 된다.

 Android는 메모리가 부족해지기 전까지는 App을 kill하지 않는다. 그렇기 때문에 실제로 onDestroy()가 항상 호출되는 것은 아니다. 하지만 Application 개발자 입장에서는 Back Button을 눌러서 Activity가 destroy된다면 onDestroy()가 호출된다고 생각해도 무방하다.



 앱을 실행하는 중에 Home Button을 누르고 다른 Application을 실행한다면 기존의 Task A는 Background로 바뀌고, 새로 실행한 Task B가 Foreground Activity가 된다. background에 있는 모든 activity는 당연히 stop 상태이다. 

사용자가 Application Icon을 클릭하거나 Overview Screen(상단에서 드래그했을 때 보여지는 화면)에서 클릭하면 다시 foreground로 바뀐다.


Activity의 Task의 동작을 이렇게 정리하면 될 것 같다.

  • Activity A에서 Activity B로 바뀔 때는 Activity A는 Stop 상태가 된다. 사용자는 Back Button을 눌러서 다시 Activity A를 resume할 수 있다.

  • 앱을 실행 중에 Home Button을 누르면 현재 Activity는 Stop상태가 되고, task는 background로 가게 된다. launcher icon이나 Overview Screen에서 클릭하여 다시 foreground로 task를 가져와서 resume상태로 바꿀 수 있다.

  • Back Button을 누르면 현재 Activity는 Stack에서 Pop되고, destroy된다. 그리고 이전Activity가 resume된다.


Task는 2가지 방법으로 관리할 수 있다.

  • manifest file에서 <activity> 의 속성을 사용

  • startActivity()를 호출하기 전에 Intent flag를 사용

 manifest에서 설정을 하면 어떤 Activity를 통해서 접근을 해와도 같은 방식으로 Task 관리를 할 수 있다. 하지만 Intent Flag를 사용하면, 특정 Activity에서 설정한 Flag로만 사용할 수 있기 때문에 기본적으로 동작되는 Task Behavior는 manifest로 설정하고 예외 되는 경우를 Flag로 설정하는 것이 좋을 듯 하다.

launchmode에는 4가지 속성이 있다.
  • standard(default) : 기본 값으로. 하나의 task에 여러 개의 인스턴스를 가질 수 있다. 즉, 액티비티가 중복해서 쌓일 수 있다.
  • singleTop : 가장 위에 있는 액티비티를 다시 호출하는 경우에는 중복해서 생성하지 않는다. A-B-C-D-D 순서로 호출할 경우 Task에는 A-B-C-D만 쌓인다. 하지만 A-B-D-C-D 로 호출할 경우 D Activity가 2번 생성된다.
  • singleTask
  • singleInstance : Task 내에서 자신만 하나의 인스턴스를 가지고 있다. 다른 Activity를 실행해도 다른 Task에 쌓인다.
 singleTask와 singleInstance는 devloper site에서 권장하지 않는 launchmode이다. standard와 singleTop만 알고 사용하되, 필요에 따라 singleTask, singleInstance를 찾아서 사용하면 될 것 같다. 주의할 점은 두 속성은 <activity>에서 <intent-filter> 에서 action이 MAIN이고, category가 LAUNCHER 인 Acitivty에 사용해야 한다.

 standard가 default이기 때문에 사람들은 이것을 기준으로 사용하지만 실제로는 singleTop을 기준으로 생각하고 작업하는 것이 좋다. 왜냐하면 같은 화면을 보고 있는데 또 똑같은 화면을 띄울 필요가 대부분 없기 때문이다. 
 다만, 문서 Activity와 같이 현재 보고 있더라도 새로운 Activity를 띄워야 할 필요가 있다. 기본적으로 singleTop을 default로 생각하고, 자신의 Activity가 이런 상황에 해당되는지를 고려하여standard로 설정하는 것이 좋다고 생각한다.

launchmode 속성 중 singleTask 부분을 보다보니 좀 특이한 경우가 있었는데, Android Browser의 경우 항상 자신의 Task를 가지도록 정의되어 있다. 그래서 내 Application에서  Browser를 사용하는 경우 Task 확인을 해보면 같은 Task가 아닌 별도의 Task가 생기는 것을 알 수 있다.

Developer 사이트를 보면 Activity가 new Task에서 시작되더라도, Back button을 누르면 유저가 이전에 보던 Acitivity로 돌아간다고 설명하고 있다. 


 그런데 위의 그림을 보면 Background Task에 있는 Activity Y를 실행하면 같은 Task에 있던 Activity X도 같이 Foreground로 올라오기 때문에 Back button을 누르면 Activity 2로 돌아가는 것이 아니라 Activity X로 돌아간다.

 내가 실행시키려는 Acitivity가 singleTask인데, 이미 Background Task에 쌓여있는 경우라면 Background Task에 있는 Task가 전부 Foreground로 올라오게 된다. 



 Android System은 사용자가 오랫동안 task를 사용하지 않을 때, root activity(stack에 가장 처음 쌓이는 Activity. main activity)를 제외하고는 전부 clear시킨다.

 그러지 않기를 원한다면 우리는 속성을 이용해서 이런 부분을 제어할 수 있다.

  • alwaysRetainTaskState : "true"일 경우. Back Stack에 있는 모든 Acitivity가 오랜 기간 유지된다.(오랜 기간이라 하는 이유는 android에서는 Application이 언제든 kill 당할 수 있기 때문에 항상 유지된다는 표현은 옳지 않다)
  • clearTaskOnLaunch : "true"일 경우. 사용자가 task를 떠났다가 돌아왔을 때, root activity를 제외 한 activity를 clear한다. 
  • finishOnTaskLaunch : 이 속서은 전체 task에 해당하는 것이 아닌 single activity에 해당된다. "true"일 경우 Acitivity를 떠났을 때(Back Button이 아닌 Home Button), Task에서 이 Acitivity는 남아있지 않는다.

Task와 Back Stack에서는 위에서 설명한 Task의 개념과 실제로는 Stack이 2개이며, Stack안에는 Task단위로 쌓인다는 것.launchmode에는 standard와 singleTop만 대부분 사용한다는 점만 포인트로 기억해두면 좋을 것 같다.



[글과 사진 출처 : http://developer.android.com/intl/ko/guide/components/tasks-and-back-stack.html ]




'Android' 카테고리의 다른 글

Fragment  (0) 2015.08.16
Tasks and Back Stack  (0) 2015.08.08
Android에서 정말 중요한 Manifest  (0) 2015.08.06
Posted by 와이빈