센서로 전환시
 PhoneWindowManager.java [onOrientationChanged]
  --> WindowManagerService.java [mWindowManager.setRotation(rotation,
                                                                          falsemFancyRotationAnimation,)]
  --> WindowManagerService.java [setRotationUnchecked(rotation,
                                                                 alwaysSendConfiguration, animFlags);]
  --> WindowManagerService.java [setRotationUncheckedLocked(rotation,
                                                                                                       animFlags);]


키패드로 화면 전환시
  --> InputManaer.java [notifyLidSwitchChanged(long whenNanos, boolean lidOpen)]

  --> WindowManagerService.java [notifyLidSwitchChanged(long whenNanos,
                                                                                               boolean lidOpen)]
  --> PhoneWindowManager.java [notifyLidSwitchChanged(long whenNanos,
                                                                                              boolean lidOpen)]
  --> PhoneWindowManager.java [updateRotation(int animFlags)]

  --> WindowManagerService.java [mWindowManager.setRotation(rotation,
                                                                          falsemFancyRotationAnimation,)]
  --> WindowManagerService.java [setRotationUnchecked(rotation,
                                                                 alwaysSendConfiguration, animFlags);]
  --> WindowManagerService.java [setRotationUncheckedLocked(rotation,
                                                                                                       animFlags);]


'안드로이드' 카테고리의 다른 글

전체 컴파일 시 Dalvic Heap Memory Size 늘리기  (0) 2011.03.09
참고 사이트  (0) 2011.03.09
GC 호출시 호출되는 함수[finalize]  (0) 2011.03.08
JNI 사용하기  (0) 2011.03.08
Memory Analyzer 로 heap dump 해석하기  (0) 2011.02.25

/** 
Garbage Collector를 이용한 finalize()호출 I 
**/ 
import java.io.*; 
class FinalMan{ 
    private FileWriter fw = null; 
    public FinalMan(String filename) throws IOException{ 
        this.fw = new FileWriter(filename); 
    } 
    public void writeData(String str) throws IOException{ 
        this.fw.write(str); 
    } 
    protected void finalize() throws Throwable, IOException{ 
        if (this.fw != null){ 
            this.fw.close(); 
        } 
        System.out.println("finalize()의 파일 닫기");         
        super.finalize(); 
    }  
} //end of FinalMan 

public class FinalManMain{ 
    public static void main(String[] args) throws Exception{ 
        System.out.println("프로그램 시작"); 
        //단순블럭 
        { 
            FinalMan fm = new FinalMan("finalize.dat"); 
            fm.writeData("안녕하세요"); 
            fm.writeData("Hello"); 
            fm = null; 
        } 
        System.gc();//가비지 콜렉터 구동 
        System.out.println("프로그램 종료"); 
    } //end of main 
} //end of FinalManMain class 
//㉶------------------------------------------------------------------㉳ 
/*** 
C:javasrcchap10>javac FinalManMain.java 
C:javasrcchap10>java FinalManMain 
프로그램 시작 
finalize()의 파일 닫기 
프로그램 종료 
***/

 

위의 예에서 FinalMan의 생성자에서 매개변수로 주어진 파일명으로 FileWriter를 생성하고 있습니다.  

◈ private FileWriter fw = null; 
◈ public FinalMan(String filename) throws IOException{ 
◈         this.fw = new FileWriter(filename); //문자 파일 스트림 생성 
◈ } 

그리고 FileWriter fw를 이용해서 주어진 문자열을 파일에 기록하는 메서드를 포함하고 있습니다. 

◈ public void writeData(String str) throws IOException{ 
◈        this.fw.write(str); //스트림을 이용해서 파일에 문자열 기록 
◈ } 

그런데 이상한 것이 있습니다. 데이터를 기록하기 위해서는 writeData()를 호출하면 되지만 FinalMan 어디에도 생성된 스트림을 닫아주는 곳은 없습니다. 즉 위의 FinalMan 클래스에서 보여주고자 하는 것은 FinalMan의 객체가 더 이상 쓸모가 없을 때 호출되는 finalize()에 스트림을 닫아주고자 하는 것입니다. 어차피 가비지 콜렉터에 의해서 finalize()가 호출될 것이고 객체가 소멸되기 전까지는 스트림을 닫지 않겠다는 것입니다. 

▣ finalize()의 재정의  
◈ protected void finalize() throws Throwable, IOException{ 
◈         if (this.fw != null){ 
◈             this.fw.close(); 
◈        } 
◈         super.finalize(); 
◈    }  

가비지 콜렉터에 의해서 객체가 소멸되면서 FinalMan의 finalize()가 호출될 것이며, 자동으로 스트림을 닫는 작업과 상위 클래스의 finalize()를 호출하는 작업이 이루어지는 것입니다. 

finalize()가 호출되는 것을 보장하기 위해서 독특한 방법을 이용하고 있습니다. 단순히 블록 처리를 하고 블록 내에서 객체를 만들고 있습니다. 그리고 블록 내에서 생성된 객체를 null로 초기화하고 있습니다. 이렇게 되면 생성된 객체가 블록 내부의 지역 변수가 되기 때문에 블록이 끝나면 가비지 콜렉터의 수집 대상이 되는 것입니다. 엄밀하게 말하면, 지역적으로 생성된 객체의 참조값이 다른 곳에서 사용되지 않는다는 것이 보장되면 가비지 콜렉터의 수집대상이 되는 것입니다. 

◈ { 
◈        FinalMan fm = new FinalMan("finalize.dat"); 
◈        fm.writeData("안녕하세요"); 
◈        fm.writeData("Hello"); 
◈        fm = null; 
◈ } 

가비지 콜렉터의 수집 대상이 되도록 만드는 것은 아주 중요합니다. 특정 블록 내에서 생성된 객체의 참조값을 다른 곳에서 사용하지 않는다는 것을 보장하기 위해서 블록 내에서 생성된 객체의 참조값을 null로 초기화하고 있습니다. 이 구문이 바로 참조값을 사용하지 않겠다고 보장하는 구문이 되는 것입니다.  

▣ null의 초기화 여부 
◈ 보통의 경우 사용하지 않는 참조값일 경우 가비지 콜렉터가 알아서 하기 때문에 별로 신경 쓰지 않는다. 하지만 사용자가 사용하지 않는 객체를 null로 명시화하면 프로그램은 더욱 유연하게 동작한다. 

가비지 콜렉터는 메모리가 부족하거나 메모리의 청소가 필요할 때 자동으로 구동되는 가상머신의 시스템 스레드입니다. 보통 이 스레드를 Finalizer라고 칭합니다. 필요에 의해서 Finalizer 스레드가 동작하지만 사용자가 직접 Finalizer를 구동하고자 한다면 다음과 같은 구문을 사용하면 됩니다. 

◈ System.gc(); 

지역적으로 선언된 객체가 더 이상 참조값을 사용하지 않는다는 것이 보장되었으며, Finalizer까지 구동되었다면, 지역적으로 선언된 객체 FinalMan fm의 finalize()가 호출되는 것을 확인할 수 있을 것입니다.


출처 : http://www.softpackage.com/bbs/bbs/content.asp?grp=devforum&board=java&num=104&opt=

안드로이드 개발을 하면서 가장 흥미롭거나 유용하다고 생각하는것 중에 하나가 JNI 이다.

C 개발된 소스를 그데로 라이브러리화 해서 java(android) 에서 사용 있다는 것은 개발자로서 축복 받은 일이다~~

작년에 프로젝트 도중에 어찌어찌 하다보니 내손에 c 구현된 천지인 소스가 들어왔다~ 생각보다 소스량이 많아 놀랐지만, 왠만한것은 할수 있다.

역시나 저작권덕에 배포는 없고 혼자 조용히 가지고만 있다ㅠㅠ

시간이 남으면 해당 라이브러리에 카메라 소스붙여서 요새 인기좋다는 투명키패드(?) 어플리케이션을 만들어 볼까 생각중이다ㅋ

잠깐 시간이 남아 천지인 소스를 안드로이드 라이브러리로 포팅해 빌드를 해보았다. jni 포팅에 대한 내용을 아래에 정리해 본다~

 

우선 ndk 설치 c 파일을 빌드하기 위해 <JNI_HOME>/<PROJECT_NAME>/project/jni 경로에 있는 Android.mk 파일을 작성해야 한다
자세한 작성법은 <JNI_HOME>/docs/ANDROID-MK.TXT (NDK 설치한 폴더)  설명되어 있다
작성법에 따라 아래와 같이 Android.mk 파일을 작성해 보았다

 

c 에서 일반적인 makefile 문법과 동일하다.

local_module 빌드명을 적는다. 이름은 빌드시에 필요한 이름이니, 적당하게 작명해서 적어준다.

bulid_shared_library 공유 라이브러리로 빌드한다는 뜻이다.

local_src_files 빌드에 필요한 c 파일들을 주루룩 나열해 주면 된다. 나열 순서는 상관 없다~

자세한 내용은 역시나 docs 폴더에 ANDROID-MK.txt 자세히 설명되어 있다. (영어로..)

ndk 아닌 다른 빌더에서 빌드된 .so 파일은 아쉽게도 정식적인 방법으로는 물릴수 없다. 요새 검색도중 크로스 컴파일해서 물릴수 있다고 하던데..

그렇게 까지 하면서 물릴만한 라이브러리를 보유하고 있지 않아 패스~

(그러고 보니.. 프로젝트도중 암암리에 얻게된 문자인식 라이브러리가 있긴하다-_-;; 이건 나중에 시간되면 해보기로 하고~)

다음 단계로 넘어가 c 구현된 소스들을 java에서 호출가능하게 하기 위해 약간의 포팅 작업을 거쳐야 한다.

C 구현

NaraAutoHandler.c, NaraAutomata.c, NaraCode_TKS.c 파일들은 automata 코드이고, NaraJni.c 파일은 c 코드와 java 코드를 연결하기 위한 포팅 레이어를 구현하였다.

나름 대범하게 원래 소스를 토씨하나 바꾸지 않고 그데로 사용하였다;; (배포하지 않을 것이기 때문에..)
포팅된 코드는 아래와 같다. 

 

#include <string.h>

#include <jni.h>

 

#include "NaraAutoDef.h"

#include "NaraAutoHandler.h"

#include "NaraAutomata.h"

#include "NaraCode_TKS.h"

 

#define S_OK               0

#define E_ERROR            -1

 

sNaraIMEState*     ime            = NULL;

char*                      buf     = NULL;

jbyteArray         jbtbuf  = NULL;

 

jint Java_com_example_keypad_KorKeypad_NaCInit( JNIEnv* env, jobject thiz , int maxsize)

{

   

    ime = (sNaraIMEState*)malloc(sizeof(sNaraIMEState));

    if(!ime)

            return E_ERROR;

   

    buf = (char *)malloc((sizeof(char) * maxsize) + 1);

    if(!buf)

            return E_ERROR;

 

    NaC_Init(ime, buf, maxsize);

   

    return S_OK;

   

}

 

void Java_com_example_keypad_KorKeypad_NaCSetCurrentMode( JNIEnv* env,

                                                  jobject thiz , int mode)

{

    NaC_SetCurrentMode(ime, mode);

}

 

void Java_com_example_keypad_KorKeypad_NaCKeyIn( JNIEnv* env,

                                                  jobject thiz , char input)

{

    NaC_KeyIn(ime, input);

}

 

 

jbyteArray Java_com_example_keypad_KorKeypad_NaCGetBuffer( JNIEnv* env,

                                                  jobject thiz)

{

 

    if (jbtbuf != NULL)

    {

            (*env)->DeleteGlobalRef(env, jbtbuf);        

    }

 

    char* src = NaC_GetBuffer(ime);

    int len = strlen(src);

    jbtbuf = (*env)->NewByteArray(env, len + 1);

    (*env)->SetByteArrayRegion(env, jbtbuf, 0, len, src);

 

    return jbtbuf;

 

}


간지나게 포팅 풀소스를 공개 했다.

(라고 해바야 별거 없다. ㅠㅠ)

원래 함수들은 많은데.. 우선 필요한 함수들 몇개만 포팅했다.

프로젝트 진행할때, virtual keypad full screen keypad input 관련된 interface 들이랑 모두 본인 일이었던 지라.. 포팅하며 오랜만에 향수(??) 느낄수 있었다 ㅠㅠ

보아도 전형적인 c 스타일의 function pointer table 이용한 방식이다~ cpp 작성하면 function 포인터가 아닌 클래스 형식으로 env->func(); 요런식으로 바뀐다. 그런데 본인은 cpp 빌드하면 자꾸 다른 빌더로 자동으로 바뀌는 바람에 c 작성해 컴파일 했다~

코드 작성 주의점은 함수명은 다음과 같은 방식을 따라야 한다

JAVA_<패지키 명>_<클래스 명>_<함수명> (JNIEnv* env, jobject thiz, <매개변수>)

 

<함수명> 에는 '_' 문자가 들어가서는 안된다처음에 못도 모르고 함수명에 언더바를 넣었다가.. java 에서 호출하는데, 자꾸 vm 죽길래 바보처럼 한참을 헤메기도 했다;;

JAVA_  java 에서 c jni 호출한다는 뜻이다. 반대로 c 에서 java 소스를 수도 있다.

패키지명과 클래스명이 일치해야 java 에서 call 가능하다.. 따라서 java 단에서도 jni 콜을 위한 전용 클래스를 하나 두어야 된다는 뜻이 된다.

VM 통해서 콜하기 때문에 속도도 왠만큼 보장된다고 어디서 본것 같다. 그래서 이미지 처리등에 관한 어마어마한 코어단 소스를 jni 만들어 호출한다고 하기도 한단다.
자바에는 포인터가 없기때문에 배열 포인터 같은 경우 jni.h 정의되있는 jbyteArray, jcharArray, jintArray 등등을 사용해야 한다
자바로 보내지는 객체는 C 단에서 메모리를 할당하기 때문에 메모리 관리에 신경 써줘야 한다.

포팅해주는 쪽에서 메모리를 관리하는게 본인에게는 편하기 때문에 이렇게 구현했다. 콜러쪽에서 메모리 관리를 해주어도 당연히 된다^^

포팅 레이어를 구현했다면, 다음은 바로 빌드를 하면 된다~

 

빌드

apps/<Project >/Application.mk APP_MODULES  apps/<Project >/project/jni/Android.mk LOCAL_MODULE 모듈명을 정확히 작성  빌드 작업은 간단하게 ndk 폴더에서 (apps, build, docs, out 폴더가 있는 ) 아래와 같이 실행하면 된다. 

 

make APP=<module >


빌드가 위와같이 성공했다면 project/libs/armeabi 폴더에 lib<모듈 >.so 파일이 생긴다. 

빌드가 완료 되었다면 이제 java 에서 호출하는 일만 남았다 ++!!

Java 구현

위와 같이 라이브러리 파일을 생성하였다면 생성된 파일을 이클립스 프로젝트에 동일하게 libs/armeabi/ 폴더에 복사  해당 라이브러리 파일에 있는 함수들을 아래와 같이 정의 한다. 

미리 구현된 소스가 아니라 처음부터 c 구현한다면 java 프로젝트 자체를 해당 폴더에 import 시키는것을 추천한다. jni 빌드하고 .so 파일 복사하고 java 컴파일하는 단계를 한번에 처리할 있다~~

 

함수 정의 시에는 c 파일와 달리 함수명만 작성하면 된다

함수를 클래스 내에 정의 했다면 라이브러리 파일이 호출 가능하다
jni 호출 구조는 간단하게 아래와 같다고 한다

 

어디서 퍼온 사진인데.. 시간이 지나서 출처가 기억나지 않는다. 안드로이드 dev 사이트 인가?? ㅡㅡㅋ;;

어쨋든 직관적으로 한눈에 호출구조를 있다. 사실 내부는 엄청 복잡하겠지만.. 단순하게 java call -> lib -> return 이라는 형식으로 단순화 하였다

 

우선 위에서 열심히 만든 .so 파일 로드 해주시고~ 바로 바로 호출해서 쓰면 된다~

 

    static {        
        System.loadLibrary("keypad-kor");        
    }

 

자바 코드 구현 주의점은 byteArray 리턴받았을 경우 한글 지원을 위해 아래와 같이 euc-kr 지원 해야한다

아직나에겐 복잡하기만한 망할 유니코드..!!

그냥 단순 간단하게 String 객체를 euc-kr 하나 생성해 주었다~ 어차피 테스트용 이니~~ 메모리따위 가볍게 무시~~

 

 

포팅된 jni 오토마타 이용하여 다음의 "ㅇㅏㄴㄷㅡㄹㅗㅇㅣㄷㅡ" 한글을 조합한 결과는 아래와 같다.

 



출처 : http://blog.naver.com/oh4zzang/40111762322


- その1: 서버 백업
- その2: 프리즈의 원인과 가비지 콜렉션
- その3: 사무라이로 heap 사용량 확인
- その4: 릭 개소를 확인하는 여러가지 방법
- その5: Memory Analyzer 로 heap dump 해석

Eclipse Memory Analyzer(MAT)를 사용하여 인스턴스가 얼마나 메모리를 잡아먹고 있는지를 알 수 있습니다.

용어해석
 *   Shallow Heap:  1개의 오브젝트가 소비하는 메모리. 1개의 참조에 대해 32 bit가 소비된다.
                               예를 들면, Integer에서는 4바이트, Long에서는 8바이트
 *  Retained Heap:  GC에 의해 제거되는 일련의 오브젝트가 사용하는 Shallow Heap의 합계.
                               오브젝트의 트리가 직접 사용하고 있는 메모리

heap dump는 JDK 에 들어있는 jmap 커맨드로 뽑아냅니다.

jmap -heap:format=x [pid]  또는  jmap -heap:format=b [pid]  의 형태로 실행하면 heap dump를 xml 형식, 또는 바이너리 형태로 추출합니다.
보통은 쌩으로 heap dump를 사람이 읽는것은 힘들기 때문에 xml 형식은 사용하지 않으며, 바이너리로 출력합니다.
바이너리 형식의 heap dump는 Eclipse Memory Analyzer 라는 툴로 해석할 수 있습니다.
Eclipse Memory Analyzer 는 SAP 가 개발한 Eclipse RPC 를 베이스로 멀티플레이트 폼 어플리케이션입니다.
꽤 괜찮은 녀석이죠.


Memory Analyzer 를 기동한 샷~!

Memory Analyzer 를 기동하면 heap dump를 간단히 읽을 수 있습니다.
[File > Open Heap Dump...] 로 폴더를 열어, heap dump를 선택합니다.


heap dump 를 열면 요롷게 보이죠

메모리 Leak 문제의 조사는 크게 2가지가 있습니다.

 1. heap 에서 차지하는 비율이 큰 오브젝트를 찾는것, 이것은 heap dump를 한개를 열어보면 해석이 가능합니다.
 2. 어플리케이션을 작동시키는 과정을 간단하게 단조증가시키면서 오브젝트를 발견하는 방법.
    구체적으로 heap dump 를 2회이상 취득해서 차분을 구하여 해석을 합니다.

"1." 에서 덩치가 큰  오브젝트 또는 대량으로 존재하는 오브젝트를 보는것으로 원인을 분석해 봅시다.
하지만 그런 오브젝트는 보통 캐쉬로 되어있는것이 많습니다. 계속 증가되는 형태가 아닌 경우는 조사해도 별볼일 없습니다.
정말 릭 되어 있는 오브젝트를 확인하기 위해서는 "2." 에서 단조증가되고 있는 오브젝트 해석법으로 해야 합니다.


1. heap 영역을 선점하는 비율이 높은 오브젝트 확인법

heap dump를  Memory Analyzer 로 열어 표시되는 over view화면에서 "Leak Suspects" 링크를 클릭!

커다란 오브젝트를 대략적으로 판단해서 픽업해 줍니다.
「대략」적으로 나오는 그림이니 반드시 Leak되어진 오브젝트가 검출된다고는 할 수 없죠

Leak Suspects 를 열었을때

위에는 org.apache.jasper.runtime.BodyContentImpl 클레스의 인스턴스가 108 개로、합계419MB(heap中72.26%)를 차지하고 있네요.
org.apache 라는 패키지로부터 Apache 프로젝트의 컴퍼넌트에 존재하는 것을 알 수 있습니다. 그리고 jasper 라는 이름은 Apache 의 jsp 코드죠.
인스탄스의 수는 108 개로 아주 많은건 아니지만 하나하나 사이즈는 심상치 않네요.
하나의 인스턴스는 뭘 기록하고 있을까요
? 함 봅시다.
이번엔 dominator tree 뷰에서 오브젝트의 속을 까봅시다.
dominator tree를 여는것은 아래 그림처럼 툴바에서 선택합니다.

dominator tree를 여는 아이콘

dominator tree 를 열면 큰 오브젝트 순서대로 소트된 인스턴스 일람이 보입니다.
그리고 이것저것 인스턴스 트리를 전개하면, 보유하고 있는 필드와 속 알맹이를 확인할 수 있습니다.

dominator tree 표시

이번엔 dominator tree 에서、BodyContentImpl 에는 꽤나 사이즈가 큰 char 배열이 있는것을 알게 되었습니다.
Inspector 페인으로 간단한 배열의 속을 미리볼 수 있지만, 부분부분 밖에 볼 수 없네요.
하지만 파일로 저장하면 상세 하게 볼 수 있습니다.


오브젝트 알맹이를 파일로 저장


보존한 char 배열의 알맹이를 확인

알맹이를 view 커맨드로 확인하면 html 이 기록되는 것을 확인 할 수 있습니다.
좀 더 자세히 보면 ・・・・썩스 그다지 크지 않는 영단어가 <div class="trackbackheader">..</div> 라는 태그에 싸여져
대량으로 들어가 있습니다.

이건 트랙빽 스펌메일 이군요.
<img border="0" src="/jira/images/icons/document_exchange.gif" width=16 height=16 align=absmiddle> 의 요소가 보이는데
 
Jira 가 들어가 있네요.
Jira 는 자신의 오픈 소스 프로젝트의 버그 트랙킹에 사용하고 있는 Web 어플리케이션입니다.
Jira 를 사용하면 뭔가 JBoss Web(JBoss 내장의 Tomcat 베이스의 컨테이너)로 Leak 하는 것일지도...

다음은 BodyContentImpl 이외에도 증가하고 있는 오브젝트가 없는지, heap 덤프의 차분도 확인해 보기로 하겠습니다.



2. 단조증가되고 있는 오브젝트 확인
heap dump의 차분을 내는것은 heap dump를 두개 열어서 히스토그램 뷰(막대 그래프의 아이콘)가 열린 상태에서 좌우의 화살표를 누릅니다.

heap dump 비교

그러면 Select baseline 이라는 다이얼로그가 뜨는데 차분을 뽑고싶은 또다른 heap dump를 선택합니다.

Select baseline에서 차분을 보고싶은 대상의 heap dump를 선택

차분이 나오면, "Objects" 즉 인스턴스 수로 소트해 봅시다.
그럼 재미있는 오브젝트가 텨 나옵니다.
com.atlassian.trackback.Trackback 라는 오브젝트가 20,211개가 증가해 있는 것을 알 수 있겠습니다.

차분을 취득해서 나온 - com.atlassian.trackback.Trackback

atlassian 은 Jira 를 개발한 회사입니다. 트랙백 스팸메일을 대략으로 접수하서 트랙백을 표현하는 오브젝트가 증가하는것 같네요.

"com.atlassian.trackback.Trackback" 로 검색한 결과 leak에 관련된 정보는 없네요.

이번엔
"jira tomcat leak"로 검색! 빙고!
05.03.04 Tomcat 6.0 - JIRA 오브젝트 메뉴얼 (3.12.2版 - 최신) - Atlassian Confluence
Tomcat 의 메노리 설정을 수정

Tomcat는, 큰 JSP의 페이지를 리퀘스트 하면 메모리 leak 되어 버립니다.
이것을 피하기 위해서, bin/setenv.sh를 다음과 같이 설정해 주세요.

export CATALINA_OPTS="$CATALINA_OPTS -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true"

제대로 문서에 써 있는 설정을하지 않았던 것이 원인이었습니다.
LIMIT_BUFFER 옵션에 대해서는 이하의 페이지로 도입의 경위가 자세하게 설명되고 있습니다.


Bug 37793 – org.apache.jasper.runtime.BodyContentImpl doesn't reset the 'cb' character array, causes memory leak

Jira 의 issue 는 여기문서 참조
[#JRA-10145] Set tomcat flag to not re-use buffers, as they result in OutOfMemoryErrors - Atlassian JIRA

아무래도, Tomcat 은 버퍼를 재이용하는 구조가 있는 것 같아서,
매우 큰 컨텐츠를 출력하면 버퍼로 heap가 넘쳐 버리는 것 같습니다.
이 기입 버퍼를 무진장하게 늘리지 않기 위한 옵션이 LIMIT_BUFFER군요.

즉, 이번 메모리 leak 문제의 발생 스텝은 이렇게 상상할 수 있습니다.
 1.  트럭 백 스팸메일을 대량으로 받아 특정의 페이지의 사이즈가 증가됨
 2.  검색 엔진의 클로러에 증가된 페이지를 열람한다
 3.  버퍼가 증가됨

서버는 Tomcat는 아니고 JBoss로 운용하고 있습니다만,
Web 컨테이너인 JBoss Web는 Tomcat  의 기반이므로 이 옵션이 제대로 효과가 있었습니다.

트랙백 오브젝트가 대량으로 있는 것은 좋은 상태인지는 모르겠습니다만,
원래 트랙백 기능은 현재의 곳에는 필요없기 때문에, 관리 화면으로부터 무효로 설정해 버렸습니다.


트랙백 기능을 OFF 로 설정

LIMIT_BUFFER 옵션의 설정과 트랙백의 OFF 설정을 실시하고 나서 1주간 이상 지납니다만,
현재의 heap 사용량은 일정하고 안정적으로 가동하고 있습니다.

메모리 릭의 트러블 슈팅은 차분히 원시 코드를 바라보거나
프로파일러를 사용해 현상을 재현시키는 조사 방법이 유명하네요.
그러나 heap 덤프나 Memory Analyzer를 사용하는 방법도 쉽게 접근 할 수 있을 것입니다.
메모리 릭 트러블은 장기간 가동하고 처음으로 나타나는 것도 귀찮은 문제입니다.
트러블이 발생하고 나서 당황하는 일이 없게, 미리 heap 덤프를 사용한 해석하는 연습을 해 두면 좋을것 같습니다.

번역하고나니 어설픈 부분도 있네요
출처: http://samuraism.jp/diary/2008/11/10/1226316240000.html

자바 힙 덤프 분석기는 jhat등 여러가지 툴이 있지만,
최근에 발견한 Eclipse Memory Analyzer 라는 프로그램이 있다.

http://www.eclipse.org/mat/

다운로드하여 압축을 해제하고, 이클립스만 수행하면 된다.
단, 기본 자바 VM을 사용할 때 발생한 HPROF 덤프 파일의 확장자는 반드시 hprof 여야만 읽을 수 있다.

작성된 힙 덤프 파일을 이 프로그램에서 읽으면 다음과 같은 화면을 제공한다.
사용자 삽입 이미지

'안드로이드' 카테고리의 다른 글

JNI 사용하기  (0) 2011.03.08
Memory Analyzer 로 heap dump 해석하기  (0) 2011.02.25
OutOfMemoryError  (0) 2011.02.22
Android Bitmap Object Resizing Tip  (0) 2011.02.22
우분투 환경에서 디바이스 드라이버 설정하기  (0) 2011.02.22
가끔씩 마주치게 되는 "OutOfMemoryError : bitmap size exceeds VM budget" 에러는 메모리 누수가 주요 원인입니다. 이와 관련된 링크를 모아봤습니다.

* 액티비티가 멈출 때 비트맵을 재활용(즉 GC)되게 하라

- bitmap 이미지인 경우 recycle() 호출
- onPause에서 수행하는게 좋음
- ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();

* 이미지를 미리 줄여서 읽어들여라

- BitmapFactory.Options.inSampleSize 활용

* Activity Context에 대한 참조(reference)를 오랫동안 유지하지 말아라

- Drawable.setCallback(null) 사용
- WeakReference를 가진 static 내부 클래스
- 이미지를 static 변수로 처리하지 마라

* 외부(outer) 클래스의 상태에 의존하지 않는 내부(inner) 클래스는 static으로 선언하라
- 내부클래스는 외부 클래스 인스턴스를 크게 만들며 또한 외부클래스 객체가 필요이상으로 오래 살아있게 되어 메모리를 더 차지할 수 있음
- 외부클래스의 상태 필드에 접근하지 않는(즉 외부 객체의 상태에 의존하지 않는) 내부클래스는 static으로 선언

* Attacking memory problems on Android

[Intro]

 

Android에서 사용하는 이미지는 Bitmap이라는 클래스에서 다~ 알아서 해줍니다.
그리고 이런 Bitmap Object를 쉽게 만들 수 있도록 도와주는
BitmapFactory 클래스 라는 것도 있습니다.

 

BitmapFactory는 여러가지 소스로 부터 Bitmap Object를 만들어 주는 일을 하는데,
전부 static이며 decodeXXX 라는 이름을 가진 메소드들로 이루어져 있습니다.

XXX에는 어떤 것으로 부터 decode를 하여
Bitmap Object를 만들어 낼지에 대한 말들이 들어 가겠죠.

 


[Decoding Methods]

 

BitmapFactory.decodeByteArray() 메소드는 Camera.PictureCallback 으로 부터 받은
Jpeg 사진 데이터를 가지고 Bitmap으로 만들어 줄 때 많이 사용 합니다.
Camera.PictureCallback에서 들어오는 데이터가 byte[] 형식이기 때문에
저 메소드를 사용 해야 하는 것이죠.

 

BitmapFactory.decodeFile() 메소드는 파일을 그대로 읽어 옵니다.
내부적으로는 파일 경로를 가지고 FileInputStream을 만들어서 decodeStream을 합니다.
그냥 파일 경로만 쓰면 다 해주는게 편리 한 것이죠.

 

BitmapFactory.decodeResource() 메소드는 Resource로 부터 Bitmap을 만들어 내며
BitmapFactory.decodeStream() 메소드는 InputStream으로 부터 Bitmap을 만들어 냅니다.
뭐 그냥 이름만 봐도 알 수 있는 것들이지요.

 


[OutOfMemoryError??]

 

보통 이미지 파일을 읽어서 Resizing을 해야 할 때가 있는데,
그럴때는 BitmapFactory로 읽어서 Bitmap.createScaledBitmap() 메소드를 사용하여 줄이면

간단하게 처리 할 수 있습니다.

 

그런데 BitmapFactory를 사용할 때 주의해야 할 점이 있습니다.
아래의 예를 한번 보시죠.

Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg");
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

이미지 파일로부터 Bitmap을 만든 다음에

다시 dstWidth, dstHeight 만큼 줄여서 resized 라는 Bitmap을 만들어 냈습니다.
보통이라면 저렇게 하는게 맞습니다.

 

읽어서, 줄인다.

 

그런데 만약 이미지 파일의 크기가 아주 크다면 어떻게 될까요?
지금 Dev Phone에서 카메라로 촬영하면
기본적으로 2048 x 1536 크기의 Jpeg 이미지가 촬영된 데이터로 넘어옵니다.
이것을 decode 하려면 3MB 정도의 메모리가 필요 할 텐데,

과연 어떤 모바일 디바이스에서 얼마나 처리 할 수 있을까요?

 

실제로 촬영된 Jpeg 이미지를 여러번 decoding 하다보면

아래와 같은 황당한 메세지를 발견 할 수 있습니다.

java.lang.OutOfMemoryError: bitmap size exceeds VM budget

네... OutOfMemory 입니다.
더 이상 무슨 말이 필요 하겠습니까...
메모리가 딸려서 처리를 제대로 못합니다.

 

이것이 실제로 decoding 후 메모리 해제가 제대로 되지 않아서 그런 것인지,
하더라도 어디서 Leak이 발생 하는지에 대한 정확한 원인은 알 수 없습니다.
이것은 엔지니어들이 해결해야 할 문제 겠죠...

 

하지만 메모리 에러를 피할 수 있는 방법이 있습니다.

 


[BitmapFactory.Options.inSampleSize]

 

BitmapFactory.decodeXXX 시리즈는 똑같은 메소드가 두 개씩 오버로딩 되어 있습니다.
같은 이름이지만 Signature가 다른 메소드의 차이점은
BitmapFactory.Options를 파라메터로 받느냐 안받느냐의 차이죠.

BitmapFactory.Options를 사용하게 되면 decode 할 때 여러가지 옵션을 줄 수 있습니다.


여러가지 많지만 저희가 지금 사용할 것은 inSampleSize 옵션 입니다.

 

inSampleSize 옵션은,
애초에 decode를 할 때 얼마만큼 줄여서 decoding을 할 지 정하는 옵션 입니다.

 

inSampleSize 옵션은 1보다 작은 값일때는 무조건 1로 세팅이 되며,
1보다 큰 값, N일때는 1/N 만큼 이미지를 줄여서 decoding 하게 됩니다.
즉 inSampleSize가 4라면 1/4 만큼 이미지를 줄여서 decoding 해서 Bitmap으로 만들게 되는 것이죠.

 

2의 지수만큼 비례할 때 가장 빠르다고 합니다.
2, 4, 8, 16... 정도 되겠죠?

 

그래서 만약 내가 줄이고자 하는 이미지가 1/4보다는 작고 1/8보다는 클 때,
inSampleSize 옵션에 4를 주어서 decoding 한 다음에,

Bitmap.createScaledBitmap() 메소드를 사용하여 한번 더 줄이면 됩니다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg", options);
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

당연한 이야기 이겠지만,
내가 원하고자 하는 사이즈가 딱 1/4 크기라면

Bitmap.createScaledBitmap() 메소드를 쓸 필요가 없지요.

 

inSampleSize 옵션을 잘 활용하면 메모리 부족 현상을 대략적으로 해소 할 수 있습니다.
참고로 제가 저 옵션을 사용한 뒤로는 메모리 에러를 본적이 한~번도 없답니다.

 


[Appendix]

 

inSampleSize 옵션을 사용하면

SkScaledBitmapSampler Object (Library Level) 를 생성 하게 되는데,
Object를 만들때 정해진 SampleSize 만큼 축소하여 width와 height를 정한 뒤에 만들게 됩니다.
그러니까 애초에 축소된 사이즈로 이미지를 decoding 하는 것이죠.

 


[Outro]

 

Android의 기본 어플리케이션 소스를 분석 하다보면
상당히 테크니컬한 기법들을 많이 얻을 수 있습니다.
어떻게 이런 방법으로 만들었나 싶을 정도로 매우 정교하고 복잡하게 만들어져 있지요.
참 대단한 것 같습니다.

 

아 그리고 왜 dstWidth와 dstHeight 변수 선언이 없냐고 따지시는 분들 설마 없겠죠?



출처 : http://blog.naver.com/visualc98/79874750


출처 : Ubuntu에서 안드로이드 디바이스 선택 안되는 문제 (아웃사이더, http://blog.outsider.ne.kr/448)

Ubuntu에서 이클립스로 Hello World튜토리얼 을 AVD가 아닌 실제 디바이스에서 실행하려고(듣던대로 에뮬은 정말 느리더군요) 넥서스원을 실행하니까 AVD를 이용한 에뮬레이터가 자동으로 뜨는 대신 Android Device Chooser 화면이 나왔습니다.(윈도우에서는 디바이스를 연결하면 자동으로 디바이스에서 실행이 되었었습니다. SDK를 설치할때 윈도에서는 드라이버를 설치했었죠.) 물론 안드로이드 디바이스에서 USB 디버깅을 활성화해야 합니다.

사용자 삽입 이미지

Linux에서는 별도의 드라이버가 필요없다고 나와있었는데 기기가 연결이 되기는 했지만 Serial Number가 ???로 나타나면서 인식을 제대로 못했는지 OK버튼이 활성화되지 않아서 다음단계로 넘어갈 수가 없었습니다.

adb devices 실행화면

안드로이드 SDK안에 있는 tools부분의 Android Debug Bridge를 이용해서도 기기를 확인해 볼 수 있는데 adb devices를 실행해도 no permissions라고 나오면서 기기명이 제대로 표시되지 않았습니다. 문서를 찾아보니 Setting up a Device for Development 에 해당부분에 대한 설명이 나와있었습니다.

/etc/udev/rules.d/51-android.rules
root권한으로 위의 위치에 51-android.rules 라는 파일을 생성하고 아래의 내용을 입력하고 저장합니다.(Ubuntu 9.10입니다.)

SUBSYSTEM=="usb", SYSFS{idVendor}=="0bb4", MODE="0666"
Logout을 하고 온 뒤에도 정상적으로 되지 않으면 데몬을 재실행시켜주어야 정상적으로 됩니다.

사용자 삽입 이미지

안드로이드 SDK의 Tools에서 adb kill-server를 실행키셔 서버를 종료시킨되 sudo ./adb start-server로 데몬을 시작시켜줍니다. adb devices를 다시 실행하면 시리얼키가 정상적으로 나오는 것을 볼 수 가 있습니다. 이제 이클립스에서 다시 안드로이드를 Run하면 정상적으로 Device를 선택할 수 있고 아래처럼 기기에서 실행된 Hello World를 볼 수 있습니다.


사용자 삽입 이미지

'안드로이드' 카테고리의 다른 글

OutOfMemoryError  (0) 2011.02.22
Android Bitmap Object Resizing Tip  (0) 2011.02.22
Task Stack 정보 불러오기  (0) 2011.02.15
안드로이드 Process 와 Static  (0) 2011.02.13
가시수명  (0) 2011.02.11
  Activity가 실행되면 Task Stack에 보관됩니다.
  현재 화면에 보이는 Activity는 Task Stack의 가장 위쪽(top)에 위치합니다.   
  
     protected void onCreate(Bundle savedInstanceState)
     {
        ....
        
        ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> task = am.getRunningTasks(1); //(숫자)는 가져올 Task의 최대갯수
 
        ComponentName topActivity = task.get(0).topActivity;
        String strPackage = topActivity.getPackageName(); //package이름.
        Log.d("Package : ", strPackage);
    }

AndroidManifest.xml에 permission을 추가해 주세요.
<uses-permission android:name="android.permission.GET_TASKS" />

저는 안드로이드 Process 에 대해 좋지 않은 추억이 몇 개 있습니다. 이전 포스트인 '안드로이드 Back & Home 그리고 종료 버튼' 에서도 한번 이야기 했지만, 어플리케이션 실행 시 특정 Process 가 시작되고, 어플리케이션이 종료되면 해당 Process 도 종료된다고 아주 평범하게 생각하고 있었고, 다르게 말하면 Activity 의 생명 주기나, Process 의 생명 주기는 비슷하다고 생각했습니다. 사소한 이 오해가 결국 큰 결과를 불러서 어플리케이션 개발 중간 쯤에 기존 어플리케이션을 왕창 수정해야 하는 아픔을 겪게 되었습니다. 


<안드로이드 Process 는 쉽게 무덤으로 가지 않는다.> 


 안드로이드 개발자 사이트에 보면 Process 의 생명주기에 대하여 다음과 같이 나와있습니다. (문제가 터지기 전에 좀 더 자세히 살펴보았다면 좋았겠지요..)

Processes and lifecycles

The Android system tries to maintain an application process for as long as possible, but eventually it will need to remove old processes when memory runs low. To determine which processes to keep and which to kill, Android places each process into an "importance hierarchy" based on the components running in it and the state of those components. Processes with the lowest importance are eliminated first, then those with the next lowest, and so on. There are five levels in the hierarchy. The following list presents them in order of importance:

 안드로이드에서는 어플리케이션의 Process 를 가능한 유지하기 위해 노력한다고 설명 되어있습니다. 이 내용을 좀더 따지고 들면, 극단적인 메모리 부족 상황이 아니면 한번 실행된 어플리케이션 Process 는 거의 죽지 않는 다고 생각할 수 있습니다. 만일 강제로 종료되는 경우라면? 비어있는 Process 라면 모를까, 무언가 일을 수행 하고 있던 Process 의 경우 안드로이드 시스템이서 해당 프로세스르 바로 다시 시작해 버립니다. 


 따라서, Process 와 연결되어 있는 각종 변수나 객체들은 개발자가 일반적으로 생각하는 것 보다 (Activity 종료 시 다 죽을거다...라던가) 죽지 않고, 아주 아주 오랫동안 살아남게 됩니다. 그 중에 하나가, 이전 포스트에서 이야기했던 Handler 를 들 수 있습니다. 이러한 원인으로 인해, 만일 개발자가 안드로이드 어플리케이션 개발을 위해서 Static 변수를 사용하고자 할 경우, 굉장히 조심할 필요가 있습니다. 그 이유는? Static 변수의 생명 주기는 해당 어플리케이션의 Process 와 같기 때문입니다. 


 간단한 예를 들어 보겠습니다. 어플리케이션 개발자가 다음과 같은 방식으로 어플리케이션을 구성했습니다. 


 1. 파일 다운로드 진척 상황을 주기적으로 감지하는 Downloader 클래스.

 2. 파일 다운로드 진척 상황의 변화를 전달 받기 위한 Listener 인터페이스.

 3. 해당 Listener 를 구현한 파일 다운로드 상황을 GUI로 표시하는 GUI Activity. 

 

  파일 다운로드를 상황을 감지하는 클래스는 현재 다운로드 중인 파일 명, 다운로드 진척사항등의 상태 정보를 가지고 있고, 해당 클래스를 참조해야 하는 Activity 가 여러 개이기 때문에, 사용상의 편의를 위하여 Singleton 형태로 구현 하기로 하였습니다. 


  Activity 는 onResume 상황에서 Singleton 클래스로 부터 다운로드 상황을 읽어와 Progress 를 표시하고(Home키로 잠시 다른 일을 수행하다가 어플리케이션 재 진입 시) 혹시 선택되어진 파일이 없을 경우(어플리케이션 최초 진입 시) 다운로할 파일을 선택하라는 Dialog 를 표시하도록 구현되었습니다.


  일반적인 경우, 개발자는 Singleton 클래스의 초기화에 대하여 신경쓸 필요가 없습니다. 최초 어플리케이션 실행 시에 자동으로 초기화 되기 때문에,  그걸로 충분하기 때문이조. 하지만 안드로이드에서는?

 

 Static 변수로 이루어진 Downloader  클래스는 Process 가 종료되지 않는 이상 해당 값을 계속 유지 합니다. 따라서 사용자가 이 어플리케이션을 최초 실행 할 때는 정상적으로 다운로드할 파일을 선택하라는 메세지가 출력되지만, 한번 어플리케이션을 종료 한 후에는 다시는 (메모리 부족으로 Process 가 종료되기 전까지) 해당 메세지를 볼 수 없습니다.


  간단한 예라서 그다지 큰 문제가 아니라고 느껴질 수도 있습니다. 하지만, 개인적인 경험으로 미루어 볼 때, 좀 더 복잡한 State 값을 관리하는데 접근의 편리함을 이유로 Static 변수나 Singleton 을 사용하게 되면, 특히 특정 Activity 가 다양한 Intent Filter 를 지원해서 여러 다른 어플리케이션에서 해당 Activity 를 마음대로 호출 할 수 있는 상황이라면, Static 변수의 값이 예측 불허 하다는 문제는 여러가지 복잡한 오류를 발생시키곤 했습니다. 


  제가 했던 실수를 반복하지 마시고, Process 가 사라지기 전에는 Activity 혹은 다른 Class 에서 선언한 Static 변수들의 값이 그대로 유지된다는 것을 기억하시고, Static Keyword 를 사용하는데 늘 조심하시길 바랍니다.

출처 : http://blog.naver.com/huewu/110081657442

+ Recent posts