카테고리 없음2014. 8. 20. 14:37
반응형

Flash 애플리케이션을 제작할때 항상 고민되는 것은 메모리와 속도일 것이다. 이 문서는 이러한 고민을 했던 분들을 위해 본인의 경험을 반영하여 작성한 것이다. 주의할 것은 이 문서에서 다루는 Object Pool은 메모리, 속도 개선을 위한 하나의 방법일 뿐이지 전부는 아니다라는 것이다. Object Pool이 어떻게 당신의 애플리케이션의 속도와 메모리를 개선시켜줄 것인가 전달하는 것이 문서의 목표이다.

목차 

  1. 객체 관리에 대한 질문!
  2. new 연산자는 비싸다!
  3. Object Pool이란?
  4. Object Pool을 도입할 때 고려사항
  5. Object Pool을 사용해보자.
  6. Object Pool과 new 연산자 사용 비교
  7. 메모리 사용의 폭이 급격한 애플리케이션은 좋지 못하다.
  8. 재사용할 수 있는 클래스 제작
  9. 정리하며
  10. 참고글



1. 객체 관리에 대한 질문!
제작된 애플리케이션에 필요한 최소한의 메모리에 대해서 생각해 본적이 있는가? 하나의 애플리케이션에 들어가는 최소 자원(resource)은 얼마나 될까? 그 자원은 얼마나 자주 사용하고 버리는 것일까? 

슈팅 게임을 보면 동일한 적군전투기가 많이 나온다. 그 중에 적군전투기1이 있다고 하자. 적군전투기1이 한 화면에 5개가 나왔다. 아군전투기에 의해 죽든 화면에서 사라지든 어쨌든지 언젠간 화면에서 사라지고 쓸모없게 된다. 좀 있다가 또 적군전투기1이 3개가 나온다. 또 반복해서 사라진다. 당신이 이 슈팅 게임을 만든다면 적군전투기1 관리를 어떻게 할 것인가? 즉, 화면에 나타날때 new로 생성하고 사라지면 delete로 메모리에게 해지요청할 것인가? 아니면 어느 특정 영역에 화면에 나타날 전투기1을 미리 5개에서 10개 생성해놓고 필요할 때마다 재사용할 것인가? 

2. new 연산자는 비싸다!
new는 클래스를 통해 객체를 생성하는 연산자이다. 이는 비교적 비용이 비싼편이다. 비용이 비싸다는 말을 구체적으로 설명하자면 new를 통해 객체를 생성하여 메모리에 올리고 다 사용했다고 판단될때 delete이나 null등을 이용해 객체참조를 제거하여 가비지 컬렉터에 맡기게 되며 결과적으로 가비지 컬렉터는 이 객체를 어느 특정시점에 제거하는 일련의 객체 생성과 삭제까지 동작이 꽤 복잡하여 메모리, CPU연산등의 자원소비가 크다라는 것을 의미한다. 특히나 클래스의 크기가 크고 복잡하다면 그 비용은 더욱 갑절로 들게 된다. new 연산 사용은 이처럼 비싼 비용을 지불해야 하기 때문에 할 수 있다면 최소한으로 사용하도록 하는 것이 속도와 메모리 관리에 도움을 준다.

앞서 설명한 슈팅 게임을 보자면 적군전투기1은 수시로 보였다가 안보였다가 한다. 이 경우 new, delete를 반복한다면 경우에 따라서 엄청난(?) 비싼 비용을 지불하게 된다. 

다른 예를 들어보겠다. 본인은 얼마전에 스타플(http://starpl.com)이라는 사이트에서 제공하는 타임라인 애플리케이션을 Flash로 개발했다. 타임라인은 사용자의 기록, 앨범등을 시간순으로 보여주게 되며 좌우측으로 이동해서 내 인생의 기록을 보는듯한 느낌을 주도록 만들었다. 이때 화면에 보여지는 사용자의 기록들이 수시로 보여졌다가 사라진다. 이때 사용된 기록을 보여주는 시각객체(DisplayObject)를 new와 delete를 반복해서 생성 및 삭제한다면 너무 비싼 비용을 들이는 것과 같다. 

스타플의 타임라인

참고글 : 스타플 타임라인 업그레이드 및 앨범 기능 추가 소식

앞의 2가지 예에서 언급했듯이 자주 보여지고 사라지는 객체를 필요할 때마다 new연산자를 사용하는 것보다는 어느 특정 영역에 미리 생성해놓고 재사용을 반복하는 구조를 도입한다면 메모리와 CPU연산에 있어서 큰 비용절감을 기대할 수 있게 된다. 

3. Object Pool이란?
Object Pool(객체 풀)은 객체를 미리 만들어놓고 담아놓는 메모리 영역이다. 개발자는 이 Object Pool에 미리 만들어진 객체를 사용하고 다 사용한 뒤에는 다시 반환시킴으로서 재사용(또는 대여)을 하도록 한다. Object Pool을 사용하면 처음 객체를 생성할때와 더 필요한 객체가 있는 경우를 제외하고 new 연산을 최소화 할 수 있다. 

4. Object Pool을 도입할 때 고려사항 
Object Pool은 미리 객체를 생성하고 지속적으로 재사용하기 위해 사용한다. 이때 미리 객체를 생성한다는 것은 초반 애플리케이션 동작 퍼포먼스에 영향을 미칠 수 있다. 

Object Pool을 사용했을때 다음과 같은 장점을 얻을 수 있다.

1. new 연산자를 자주 사용해야하는 경우에 도입하면 애플리케이션 속도 향상을 기대할 수 있다. 
2. 가비지 컬렉터에 의존하지 않는 메모리 관리를 꾀할 수 있다.

하지만 일반적으로 10개가 필요한데 미리 100개를 만들어 항상 10% 미만 정도의 재사용률을 보인다면 그것은 메모리 낭비이다. 그러므로 다음과 같은 조건에서 Object Pool을 사용하고 관리하는 것이 좋겠다.

1. 비교적 큰 용량의 객체를 자주 사용되거나 반환해야하는 경우에 사용한다.
2. 미리 생성할 객체를 적당하게 정하도록 한다.

반대로 위 경우가 아니라면 쓸데없이 Object Pool을 사용하지 말자. 


5. Object Pool을 사용해보자.

폴리고널 lab에서 간단한 Object Pool을 공개했다. 아래 링크에서 다운로드 받을 수 있다.


사용 방법은 매우 간단하다. ObjectPool 클래스는 아래 코드처럼 특정 클래스를 할당(allocate)하면 된다.

var isDynamic:Boolean = true;

var size:int = 100;

var pool:ObjectPool = new ObjectPool(isDynamic);

pool.allocate(MyClass, size);


isDynamic 플래그는 Object Pool내의 객체가 비어있는가 체크하기 위해 사용한다. 즉, true이면 Pool은 자동적으로 정해진 크기(size)를 넘어서더라도 Pool의 크기를 확장하여 새로운 객체를 줄 수 있으나 false이면 정해진 크기만큼 객체를 이미 준 경우에는 Pool이 비게 되므로 Error를 발생시킨다. 

크기(size)를 100으로 지정했기 때문에 미리 Object Pool에 100개의 MyClass의 객체를 생성해 두도록 한다. 

이제부터 MyClass는 아래와 같은 방식으로 사용하면 되겠다.

myObjectArray[i] = new MyClass();

// 코드 대신 아래를 사용한다.

myObjectArray[i] = pool.instance;


다 사용한 MyClass객체는 Object Pool에 반환해줘야 한다. 이때 가비지 컬렉터의 기능을 이용하는게 아니라 재사용(recycle)을 위해서 다음과 같이 코딩하면 되겠다.

pool.instance = myObjectArray[i];


사용방법은 이게 전부다. 

원한다면 자신의 애플리케이션에 맞게 이 공개된 ObjectPool을 개선시킬 수 있다. 가령, 100개의 Pool을 만들어놓지만 처음부터 100개를 생성해놓지는 않겠고 필요할 때마다 생성해주는 구조인 게으른(lazy) 생성을 기대할 수 있을 것이다. 또는 DisplayObject의 경우라면 사용한다는 것을 DisplayObject.parent 속성으로 판단할 수 있다. 위 처럼 pool.instance = '다 사용한 객체'와 같은 형태로 반환하는 것보다 자신의 부모가 존재하지 않는다면 재사용할 객체로 판단하도록 만들면 일종의 자동 Object Pool을 만들 수 있다. 자동 Object Pool은 다음 링크를 참고하면 좋겠다. 

자동 풀링(auto pooling)을 구현해보자.



6. Object Pool과 new 연산자 사용 비교 

Object Pool을 사용해야하는 타당성은 퍼포먼스 실험을 통해 쉽게 알 수 있다.  이러한 점에서 폴리고널 랩에서 제공하는 실험은 매우 유용한 자료이다. 

실험 방법은 아래와 같다. (폴리고널 랩에서는 이 실험을 Window Vista, Flash Player 9.0.124에서 진행했다.) 그리고 Pool크기는 100으로 지정했다. 

첫번째로 아래 코드처럼 k수 만큼 Object Pool로 부터 객체를 get만 한다.

for (var i:int = 0; i < k; i++) instances[i] = p.instance;


두번째로 아래 코드처럼 k수 만큼 Object Pool로부터 객체를 get/set 한다.

for (i = 0; i < k; i++) instances[i] = p.instance;

for (i = 0; i < k; i++) p.instance = instances[i];


세번째로 아래 코드처럼 k수 만큼 Object Pool대신 new 연산자를 사용한다.

for (i = 0; i < k; i++) instances[i] = new MyClass();



결과는 다음과 같다.


네이티브 Object 클래스의 경우 Object Pool을 사용하는 것이 new 연산자를 사용한 것보다 5배 정도 빨랐다.


Object 보다 더 크고 복잡한 flash.geom.Point의 경우는 약간더 빠른 결과를 보인다.


 flash.display.Sprite 객체를 가지고 하면 무려 80배나 속도 개선이 되었다. 이는 복잡하고 큰 클래스에 Object Pool을 도입하면 효과적일 수 있다고 판단할 수 있다.

이 실험을 통해 적절한 방법으로 Object Pool을 사용하는 것은 애플리케이션의 퍼포먼스 증가에 지대한 영향을 줄 수 있다는 것을 깨달을 수 있다.


7. 메모리 사용의 폭이 급격한 애플리케이션은 좋지 못하다.
본인은 어떤 애플리케이션(Flash가 아니더라도)이든지 그 애플리케이션에 필요한 최소한의 메모리는 항상 존재한다고 생각한다. 무조건 메모리 사용을 줄여보겠다고 new, delete를 반복하는 것은 잘못된 것이다. new, delete를 반복하면 오히려 CPU 및 메모리 사용의 반복을 부축이며 애플리케이션의 전체적인 퍼포먼스를 저하시키는 경우가 발생할 수 있다. 실제로 new,delete를 반복하는 구조로 개발한 애플리케이션을 Flash Builder의 프로파일링 기능을 통해 메모리 사용을 살펴보면 큰폭으로 메모리 사용/해지가 일어난다는 것을 확인할 수 있다. 그러므로 몇몇 사람들이 Object Pool과 같은 개념과 같이 퍼포먼스 향상에 도움이 되는 개념을 전혀 사용하지 않고 Flash는 느리고 가비지 컬렉터 동작은 비정상적이다라고 말하는 것은 섣부른 판단이다라고 생각한다. 얼마든지 메모리를 효율적으로 사용하고 속도 개선을 할 수 있음에도 불구하고 잘못된 개발 방식때문에 전체적인 퍼포먼스가 나빠지는 것은 결국 개발자의 잘못인 것이다. 이는 Flash든 어떤 언어든 동일한 생각으로 접근해야 한다.

Flash Builder의 프로파일링 기능을 이용해 Object Pool과 new 연산자간의 메모리 변화를 확인할 수 있는 간단한 실험을 해보겠다. (프로파일링 기능을 사용하는 방법은 주제에서 벗어나므로 제외하겠다.)

먼저 Shape객체를 확장해서 크기와 색이 렌덤하게 그려지는 클래스를 만든다.

//화면에 출력할 Shape

class RandomShape extends Shape {

        static private var colorPool:Array = [0xff0000,0x00ffff,0x0000ff,0x00ff00,0x0f0f0f,0xf0f0f0,0xffff00,0xf00cf0,0x00fcaa,0xff0c9a];

        public function RandomShape():void {

        }

        public function draw():void {

               var radius:Number = Math.random() * 40;

               var color:uint = colorPool[ Math.floor(Math.random()*9) ];

               graphics.clear();

               graphics.beginFill(color,1.0);

               graphics.drawCircle( 0, 0, radius );

               graphics.endFill();

        }

}


위 클래스를 가지고 자동 Pool을 구현한 클래스를 만든다.(참고 : http://www.diebuster.com/?p=1000)

//Shape 자동 객체 Pool

class RandomShapeAutoPool {

        private var _pool:Vector.<RandomShape>;

        public function RandomShapeAutoPool() {

               //적당히 수로 초기화한다.

               _pool=new Vector.<RandomShape>();

        }

 

        public function getShape():RandomShape {

               var key:*, result:RandomShape;

               //먼저 기존의 pool에서 찾아본다.

               for (key in _pool) {

                       //만약 해당 객체가 null이라면 new 통해 생성한다.

                       if (_pool[key] === null) {

                              _pool[key]=new RandomShape;

                              result=_pool[key];

                              break;

                       //null 아니라면 parent 조사하여 사용가능한지 판단한다.

                       else if (_pool[key].parent === null) {

                              result=_pool[key];

                              break;

                       }

               }

               //기존의 pool안에서 쓸만한  찾지 못했다면

               if (result === null) {

                       result=new RandomShape;

                       _pool[_pool.length]=result;

               }

               //인스턴스를 반환한다.

               return result;

        }

}


이제 테스트할 수 있는 호스트코드를 제작해보겠다.

package {

        import flash.display.*;

        import flash.events.*;

        import flash.text.*;

        import flash.utils.*;

 

        [SWF(backgroundColor="#ffffff", frameRate="60", width="400", height="400")]

        public class ObjectPoolTest extends Sprite {

               private var pool:RandomShapeAutoPool=new RandomShapeAutoPool;

               private var poolFlag:Boolean = false;

               private var textPoolFlag:TextField;

               private var shapeCanvas:Sprite;

 

               public function ObjectPoolTest() {

                       addEventListener(Event.ADDED_TO_STAGE, ADDED_TO_STAGE);

               }

 

               private function ADDED_TO_STAGE($e:Event):void {

                       removeEventListener(Event.ADDED_TO_STAGE, ADDED_TO_STAGE);

                       stage.scaleMode=StageScaleMode.NO_SCALE;

                       stage.align=StageAlign.TOP_LEFT;

                       //rf)http://blog.jidolstar.com/656

                       if (stage.stageWidth === 0 && stage.stageHeight === 0) {

                              stage.addEventListener(Event.ENTER_FRAME,function($e:Event):void {

                                             if (stage.stageWidth > 0 || stage.stageHeight > 0) {

                                                     stage.removeEventListener($e.type, arguments.callee);

                                                     init();

                                             }

                                      });

                       else {

                              init();

                       }

               }

 

               private function init():void {

                       //shape 부모객체

                       shapeCanvas = new Sprite;

                       addChild( shapeCanvas );

                       //PoolFlag 상태표시

                       textPoolFlag = new TextField();

                       textPoolFlag.text = "poolFlag=" + poolFlag;

                       addChild(textPoolFlag);

                       //마우스 클릭 영역

                       graphics.beginFill(0x000000,0);

                       graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);

                       graphics.endFill();

                       addEventListener(MouseEvent.CLICK, MOUSE_CLICK);

                       //반복동작     

                       addEventListener(Event.ENTER_FRAME, ENTER_FRAME);

               }

 

               private function MOUSE_CLICK($e:MouseEvent):void {

                       //마우스 클릭때마다 poolFlag 상태를 전환

                       poolFlag = !poolFlag;

                       textPoolFlag.text = "poolFlag=" + poolFlag;

               }

              

               private function ENTER_FRAME($e:Event):void {

                       var shape:RandomShape, i:int, numChildren:int;

                       //poolFlag 따라서 new연산자 또는 Object Pool에서 객체 참조

                       shape = poolFlag ? pool.getShape() : new RandomShape;

                       shape.draw();

                       shape.x = Math.random() * stage.stageWidth;

                       shape.y = 0;

                       shapeCanvas.addChild( shape );

                       //계속 아래로 떨어뜨림화면에서 벗어나면 removeChild시킴

                       numChildren = shapeCanvas.numChildren;

                       for( i=0; i<numChildren;i++) {

                              shape = shapeCanvas.getChildAt(i) as RandomShape;

                              shape.y += 5;

                              if( shape.y > stage.stageHeight ) {

                                      shapeCanvas.removeChild( shape );

                                      i--;

                                      numChildren--;

                              }

                       }

               }

        }

}


동작은 매우 단순하다. poolFlag가 true이면 제작한 자동 Object Pool을 사용하겠다는 것이고 false이면 new 연산자를 사용해 RandomShape객체를 생성하겠다는 것이다. poolFlag 전환은 마우스 클릭으로 할 수 있다. 그리고 비가 내리는 것처럼 Shape 객체는 위에서 부터 아래로 떨어지며 맨 아래에 닿으면 removeChild 시킨다.

테스트 화면


실행 코드와 결과물은 http://wonderfl.net/code/805dc65d1f7800b7bee5dc3cd72ca21c0a87c2d6 에서도 볼 수 있다.

이제 Flash Builder의 프로파일링 기능을 이용해 메모리 변화 및 객체 생성빈도를 살펴보자.

먼저 new 연산자를 사용했을때 메모리 변화를 보자. 

new 연산자로 객체생성시 Memory Usage


new 연산자를 사용해 객체를 생성하고 필요없을때 removeChild를 하게 되면 가비지 컬렉션 처리가 되기 때문에 일정한 시점에 메모리가 해지되는 것을 반복하는 것을 확인할 수 있다.

new 연산자로 객체생성시 Live Object


위 표는 축적된 객체수(Cumulative Instances), 현재 객체수(Instances), 축적된 메모리(Cumulative Memory), 현재 메모리(Memory)를 나타낸다. 변화 무쌍하며 실제로 보여지지 않는 객체도 가비지 컬렉션 처리가 일어날때까지 메모리에 남아 있다. 분명 비효율적으로 보인다.

반대로 Object Pool을 이용했을때 메모리 변화이다.

Object Pool로 객체관리시 Memory Usage

new 연산자를 사용했을때와 비교가 안될 정도로 고요한 느낌이 든다. 거의 일직선의 메모리 사용율을 보인다.

Object Pool로 객체관리시 Live Object


축적되는 객체도 81개를 넘지 않는다. 그만큼 객체 생성과 삭제에 민감하지 않게 되고 가비지 컬렉터 도움을 받지 않고 객체관리가 되는 것을 확인할 수 있다.


8. 재사용할 수 있는 클래스 제작

Object Pool을 사용시 한가지 고려할 사항은 재사용할 수 있는 클래스를 만들어야 한다는 점이다. 왜냐하면 Object Pool은 단순히 재사용을 위한 공간만 제공하는 것이지 재사용을 하기 위한 어떤 기능을 클래스에게 줄 수 없기 때문이다. 그래서 재사용이 가능한 클래스를 설계하는 것은 하나의 기술적 이슈가 될 수 있다.  

재사용할 수 있는 클래스를 어떻게 만드는지 간단한 예를 들어보겠다.

final public class MyData {

        public var type:String;

        public var id:int;

        public var name:String;

}


위 클래스는 데이터를 담는 클래스이다. id, name은 실제 사용하는 데이타 값들이고 이 데이타의 종류는 type으로 구분한다. 이 데이타를 사용하는 인터페이스를 제작해보겠다.

public interface IMyClass {

        function init($data:MyData):void;

        function clear():void;

        function get data():MyData;

}


이 인터페이스는 앞으로 만들 MyClass01, MyClass02... 등을 구현하기 위한 것이다. 초기화 하기 위해 init()함수가 있고 인자로 위에서 제작한 MyData 객체를 받는다. 또한 사용할 필요가 없을때 내부 청소를 위해 clear()함수를 추가했다. 게다가 가지고 있는 data를 참고하기 위해 get data()도 만들었다. 이제 이 인터페이스를 구현한 클래스는 아래처럼 만든다.

final internal class MyClass01 extends Sprite implements IMyClass {

        private var _data:MyData;

        public function MyClass01() {

        }

        public function init($data:Mydata):void {

               _data = $data;

               //구현

        }

        public function clear():void {

               _data = null;

        }

        public function get data():MyData {

               return _data;

        }

}

 

final internal class MyClass02 extends Sprite implements IMyClass {

        private var _data:MyData;

        public function MyClass02() {

        }

        public function init($data:Mydata):void {

               _data = $data;

               //구현

        }

        public function clear():void {

               _data = null;

        }

        public function get data():MyData {

               return _data;

        }

}

 

final internal class MyClass03 extends Sprite implements IMyClass {

        private var _data:MyData;

        public function MyClass03() {

        }

        public function init($data:Mydata):void {

               _data = $data;

               //구현

        }

        public function clear():void {

               _data = null;

        }

        public function get data():MyData {

               return _data;

        }

}



코드가 길어보이지만 3개 클래스 모두 IMyClass를 구현했고 Sprite를 확장했다. 단지 클래스 이름만 다르며 실제 구현부는 알아서 구현하면 된다. 

개발자는 MyClass01, MyClass02, MyClass03은 매우 자주 new 연산자로부터 생성되는 클래스로 판단했고 그래서 Object Pool을 도입하겠다고 결정했다고 하자. 그럼 다음과 같이 시도해 볼 수 있다.

import de.polygonal.core.ObjectPool;

import flash.display.DisplayObject;

 

public class MyPool {

        private static var poolList:Object;

       

        static public function init():void {

               poolList = {

                       'type01':new ObjectPool(true),

                       'type02':new ObjectPool(true),

                       'type03':new ObjectPool(true),

               };

               poolList.type01.allocate(20, MyClass01);

               poolList.type02.allocate(10, MyClass02);

               poolList.type03.allocate(100, MyClass03);

        }

       

        static public function getObject($data:MyData):IMyClass {

               if( $data === null ) {

                       throw new Error('인자값은 null이면 안됩니다.');

               }

               try {

                       var object:IMyClass = poolList[$data.type].object;

                       object.init($data);

               catch {

                       throw new Error('데이타의 type값이 잘못된 값입니다.');

               }

               return object;

        }

       

        static public function returnObject($object:IMyClass):void {

               if( $object === null ) {

                       throw new Error('인자값은 null이면 안됩니다.');

               }

               $object.clear();

               if( DisplayObject($object).parent ) {

                       DisplayObject($object).parent.removeChild( DisplayObject($object) );

               }

               poolList[$object.data.type].object = $object;

        }

}



위에서 제작된 MyPool은 static클래스이며 ObjectPool을 이용해 IMyClass 인터페이스를 구현한 클래스를 init()함수에서 적당하게 할당하는 것을 확인할 수 있다. 또한 getObject()를 통해 인자값 data를 참고하여 참조할 Object Pool을 찾아 IMyClass를 구현한 각각의 클래스의 객체를 받아올 수 있으며 returnObject를 통해 다 사용한 객체를 반환한다. 

실제로 실무에서 이런 형태로 개발했고 이는 매우 유용했다. MyPool 클래스를 더 개선해서 더 많은 수 Object Pool을 감당할 수 있도록 확장할 수 있다면 더욱 유용해질 것이라 생각한다.


9. 정리하며 

Object Pool 개념은 Flash에만 국한되는 유용한 개념이 아니다. 이런 개념들에 대한 노하우를 계속 쌓아간다면 당신의 애플리케이션은 더욱 좋은 작품으로 탈바꿈할 수 있을 것이다. 

Flash Player가 느리고 메모리 관리가 안된다고 생각하지는 말자. Flash Player 태생자체가 그런것을 어찌하겠는가? 제작한 애플리케이션이 느리다면 그것은 결국 개발자가 잘못한 것임을 항상 인지하자. 중요한 것은 여전히 Flash Player는 유용하고 많이 사용되고 있으며 지금도 나날이 발전하여 Cross OS, Cross Browser를 넘어 Cross Device 세계로 뻗어가고 있다는 점이다.


10. 참고글

글쓴이 : 지돌스타(http://blog.jidolstar.com/666

Posted by 1010
00.Flex,Flash,ActionScript2014. 8. 20. 14:30
반응형

나는 최근에 [Flex / AIR / ActionScript3] Event 청취자와 메모리 관리 에 대한 글을 썼다.

그러나 놀랍게도 "불꽃남자"님께서 다음과 같이 지적해주셨다.

Flex의 메모리 문제에 대한 마지막 방법이
weakly reference로 모든 객체를 다루는것이지만...
무수한 테스트에 의해도....
결국 메모리는 털어지지 않습니다.
일반적인 OOP에서 메모리를 sweep하는 프로세스는
스레드가 담당을 하게 되고...그 스레드만의 stop the world를
통해 다른 실행중인 스레드가 영향을 받지 않게 해야지만,
AS3의 GC에는 그러한 구조가 없습니다.
weakly reference가 잘 동작하지 않는다고 Adobe측에 아주 강력하게
항의를 했지만, 돌아오는 답은 원래 그런거다...라는...
어이가 없죠?ㅋㅋㅋ
Flex Product Manager에게도 강력히 요청했으나.
Flex 3에서도 변한건 없습니다.
Large Application에서 GC를 정확히 할려면,
편법으로 밖에 할 수 없겠죠...

참고로..Flex SDK안의 많은 오브젝트에 addEventListner가 구현되어있지만,
removeEventListner가 없는 코드가 더 많다는..ㅡ,ㅡ;;
이부분 역시 어도비에 따졌지만,
돌아오는 답은...원래 그런거다..그냥 써라..
역시 어이가 없죠?ㅋㅋㅋ
자기들이 가이드라인해준 방식을 SDK안에서 조차 적용이 안되어있는걸 
어떻게 설명해야하나요?ㅡ,ㅡ;;

아..그리고 위의 코드에서
objects.destroy();
objects = null;
을 호출하게 되면, 이론상으로 잘 돌아가야는데...
원래 OOP의 CG구조가 저렇게 destroy()를 시키고, null을 대입하면
바로 GC가 일어나는건 아닙니다.
하지만 최 우선순위를 두고 메모리에서 없애야 하는데,
Flash Player에서 그 타이밍이 언제인지는 아무도 모릅니다.
다만, 

어도비에서 주장하는 바는 웹브라우저가 가용할 수 있는메모리, 혹은
Flash Player가 가용할 수 있는 메모리의 임계치까지 다 찼을때만
GC가 발생한다고 합니다.


이게 말이나 되는 일입니까?ㅎㅎ
그럼 destroy() 메소드는 왜 만들었으며, 객체에 null을 대입하는
코드는 무슨 소용이겠습니까?
좋은글 올려주셨는데, 우리가 생각하는데로 Flex가 동작하지 않기에
몇마디 달아봤습니다.ㅎㅎㅎ
수고하세요 지돌스타님~~~^^

용호님이 테스트 하셔서 좋은 정보 올려주세요.
그리고 위에서 보여주신 정보는 상당히 좋은 정보입니다.
보통 자바개발자라면 저러한 부분을 생각하지만,
그렇지 않은 분들도 많거든요.
외국에서도 weakly reference하게 개발하자고 많은 분들이 주장하시고 있고,
말씀하신 remove관련된 내용은 분명히 어플리케이션의 부하를 줄여주는
좋은 방법입니다.
메모리 문제 때문에 악받은 Flex 챔피언들 몇명있죠..ㅋㅋ
동호대표, 진욱대표...그리고 저..ㅋㅋㅋ
어도비 수석 엔지니어의 말로는 Flash Player 10이 나오면
해결 가능할것 같다...같다...라고 했습니다..ㅎㅎ



결국 무슨말이고하니.... 
이런식으로는 가비지 컬렉터(GC)가 바로 동작하지 않는다는 것이다.
그래서 실제로 그러한가 테스트 해보았다.
그랬더니... 정말 그러한 것이 아닌가?

수백개의 자식 컴포넌트를 추가한뒤 참조까지 삭제후 부모에서 떼어냈는데도... System.totalMemory 값은 거의 불변이었다. 재미있는 것은 다시 수백개의 자식을 추가하는 동안 갑자기 System.totalMemory가 증가만 하는것이 아니라 뚝 떨어지는 시기가 있다. 즉, 프로그래머는 메모리 관리는 내 마음대로 할 수 없다는 것을 의미한다. Flash Player가 알아서 어느정도 GC 대상이 만들어졌을 경우 삭제한다는 것이다.

한가지 중요한 점은 [Flex / AIR / ActionScript3] Event 청취자와 메모리 관리 글에서 쓴 것처럼 최소작업은 해야 그나마 Flash Player의 GC가 동작할 수 있다는 것이다. 그래서 쓸데없는 글은 아니였구나 생각했다. 

더 재미있는 것이 있다.
바로 System.gc() 함수이다.
이 함수는 GC를 강제적으로 실행해준다. 그...그런데....

gc()method 
public static function gc():void

Forces the garbage collection process.

For the Flash Player debugger version and AIR applications only. In an AIR application, the gc() method is only enabled in content running in the AIR Debug Launcher (ADL) or, in an installed applcation, in content in the application security sandbox.

Player Version: Flash Player 9 Update 3 or the AIR Debug Launcher (ADL).

Flash Player debugger와 AIR Applications 에서만 동작한단다.... 
즉, 배포용 Flash/Flex 프로그램에서는 동작하지 않는다는 말이다.
왜 이렇게 만들었지???? ㅡㅡ;;;;;

테스트 소스를 받아봐서 실행해보길 바란다(Flex 3기반)



 이 소스는 http://wooyaggo.tistory.com/search/system.gc 에서 제공한 소스를 참고했다.

만약 Flex나 Flash 프로그래머라면 Flash Player가 debug버전으로 설치되어 있을 것이다. 그 상태에서는 System.gc()가 잘 동작하므로 GC가 매우 잘 동작하는 것처럼 보일것이다. 하지만 

http://wooyaggo.tistory.com/112 

에서 Flash Player Uninstaller를 이용해 debug용 Flash Player를 삭제한 후 보통 Flash Player 를 설치후 방금 받은 소스를 실행해보자.
그럼 System.gc()는 제대로 동작안되고 GC가 동작되는 시기가 Flash Player에서 임의로 정해진다.

결론적으로 이러한 환경에서는
되도록이면 객체를 최소한 만들고 
모듈화를 최대한 활용하며 
재활용할 수 있는 것은 재활용하면서 사용하는 것이 
메모리관리에 도움된다.

아~~~ 다 좋은데.... 왜 메모리가 이렇게 골치아프게 만드는 것이냐.... ㅡㅡ;;

잘못된 내용이나 추가할 사항이 있다면 언제든지 지적해주세요.
이러한 사항은 함께 공유해야 한다고 생각하거든요.

읽을거리 : AIR/Flex: Memory optimisation 

글쓴이 : 지돌스타(http://blog.jidolstar.com/319)

Posted by 1010
00.Flex,Flash,ActionScript2014. 8. 20. 14:27
반응형
참고 : Adobe Flex3 실전 트레이닝 북, 해외 블로그, 국내 블로그
이 글은 어도비리아에 기술문서로 제출 되었으며, 동일한 내용을 어도비리아에서 볼 수 있습니다.



-2부 프로파일링-
<목차>
1. 프로파일링이란?
2. 가비지 콜렉션
2-1. 레퍼런스 카운팅
2-2. 마크앤스윕
3. Flex Profiling perspective
3-1. 메모리 프로파일링
3-1-1. Profile 뷰
3-1-2. Memory Usage 뷰
3-1-3. Live Objects 뷰
3-1-4. Memory Snapshot 뷰
3-1-5. Object References 뷰
3-1-6. Loitering Objects 뷰
3-1-7. Allocation Trace 뷰
3-1-8. Object Statistics 뷰
3-2. 퍼포먼스 프로파일링
3-2-1. Performance Profile 뷰
3-2-2. Method Statistics 뷰
4. 맺음말


1. 프로파일링 이란?
애플리케이션을 개발하고 실행하게 되면 알 수 없는 원인에 의해 느려지거나 심지어 멈추는 경우가 종종 발생한다. 디버깅만으로는 이런 알 수 없는 원인을 찾아내기란 쉽지 않다. 이런 경우 애플리케이션의 성능측정을 통해 어느 부분에서 어떤 문제가 발생하는지를 찾아낼 수 있다. 성능측정을 전문적인 용어로 프로파일링 이라고 한다.
프로파일링은 디버깅과 더불어 소프트웨어 품질 향상에 도움을 준다. 

우리는 Flex Builder3 Professional(이하 플렉스빌더) 에서 제공하는 프로파일링 기능을 이용할 것이다.
(Flex Builder Standard 버전은 프로파일링 기능을 제공하지 않는다.)
플렉스빌더에서 프로파일링을 하기전 플래시 플레이어의 가비지 콜렉션에 대한 이해가 필요하다.
따라서 먼저 가비지 콜렉션에 대해 알아보겠다.


2. 가비지 콜렉션
가비지 콜렉션이란, 플래시 플레이어의 숨겨진 프로세스인 "가비지 콜렉터"에 의해서 참조 되지 않는 객체들을 메모리에서 해제 하는 것을 말한다. 참조 되지 않는 객체가 있다고 해서 
가비지 콜렉션이 무조건 수행되는건 아니다.가비지 콜렉션이 언제 어느 시점에서 일어나는지 정확하게 알 수는 없지만 짐작할 수 있는 시점은 현재 애플리케이션이 플래시 플레이어가 OS로부터 빌려온 메모리 보다 더 많은 메모리를 요구할 때 이다.

가비지 콜렉터는 객체가 더 이상 참조되지 않는지 확인하기 위해 레퍼런스 카운팅과 마크앤스윕 이라는 두 가지 절차를 따른 후 가비지 콜렉션을 수행한다. 이 두 가지 절차를 이해하는 것은 플렉스 프로파일링 기능을 이용하는데 도움을 줄 것이고 메모리를 효율적으로 이용하는 애플리케이션을 작성하도록 도움을 줄 것이다.

그럼 가비지 콜렉션을 위한 두 가지 절차에 대해 알아보자.


2-1. 레퍼런스 카운팅
레퍼런스 카운팅은 ActionScript 1.0 때 부터 사용되어 왔으며, 객체가 가비지 콜렉션의 대상인지 확인하는 가장 쉽고 빠른 방법이다. 어떤 객체를 생성하고 
참조값(해당 객체의 메모리 주소 값)을 레퍼런스(참조변수)에 할당하면 레퍼런스 카운트가 1 증가한다. 이 객체의 참조값을 또 다른 레퍼런스에 할당하면 레퍼런스 카운트가 1 증가되어 총 2가 된다. 만약 두개의 레퍼런스중 하나의 레퍼런스에 null을 할당하게 되면 레퍼런스 카운트는 1이 감소하며, 남은 하나의 레퍼런스 마저 null을 할당하면 레퍼런스 카운트는 0이 되어 버린다. 가비지 콜렉터는 레퍼런스 카운트가 0인 것들을 가비지 콜렉션의 대상으로 삼는다. 

다음 예제를 보면 쉽게 이해할 수 있을 것이다.



위 예제에서 생성된 Object는 레퍼런스 카운트가 0이기 때문에 가비지 콜렉션의 대상이 된다.
하지만 레퍼런스 카운팅 만으로는 상호참조에 대한 문제를 해결할 수 없다.

상호참조가 무엇인지 다음 예제를 통해 살펴보자.



위 예제에서 생성된 두개의 Object는 obj.foo와 obj2.foo 에서 상호참조 되고 있다.
obj와 obj2에 null을 할당하더라도 이 두개 Object의 레퍼런스 카운트는 결코 0이 될 수 없다.
이런 상황은 매우 빈번하게 일어나며 더욱 복잡해질 수 있다. 플래시 플레이어7 까지는 XML 객체에서 노드들의 상호참조 때문에 이슈가 되기도 했는데, 다행히 플래시 플레이어8에서 마크앤스윕 이라는 새로운 방법이 등장해 이 문제를 해결하고 있다.


2-2. 마크앤스윕
플래시 플레이어(버전 8이상) 는 루트객체(Application 자체 또는 메인MXML) 부터 시작해서 루트객체가 참조하는 객체들과 그 객체들(참조 당하는 객체들)이 참조하는 객체들을 마킹한다. 여기서 마킹의 의미는 객체가 가비지 콜렉션의 대상이 되지 않게 하기 위해 점 찍어두는 것이다. 이 처럼 플래시 플레이어는 애플리케이션의 모든 객체트리를 돌며 참조되고 있는 객체들을 마킹한다. 마킹된 객체들은 레퍼런스 카운트가 무조건 1이상이기 때문에 결코 가비지 컬렉션의 대상이 되지 않는다. 반면 마킹되지 않은 객체들은 가비지 컬렉션의 대상이 된다. 마킹되지 않은 객체는 레퍼런스 카운트가 0일거라고 생각할 수 있지만 그렇지 않다. 플래시 플레이어는 오직 루트객체 까지 연결된 객체만 마킹하므로, 루트객체 까지 연결되지 않은 상호참조 객체들은 마킹되지 않는다. 따라서 상호참조 객체들은 레퍼런스 카운트가 1이상이라도 루트객체에 연결되어 있지 않으면 가비지 콜렉션의 대상이 된다.

다음 그림을 통해 마킹된 객체와 마킹되지 않은 객체들을 파악해보자.



(위 그림은 어도비에서 제공하는 가비지 콜렉터 시뮬레이션이다.http://www.adobe.com/devnet/flashplayer/articles/garbage_collection/example2.html 에서 직접 시뮬레이션 해볼 수 있다.)

위 그림에서 초록색은 루트객체에 연결된 마킹된 객체들이고 빨간색은 상호참조로 인해 레퍼런스 카운트가 각각 1 이지만 루트객체에 연결되지 않았기 때문에 가비지 콜렉션이 수행되면 빨간색 객체들은 메모리에서 제거될 것이다.

지금까지 플래시 플레이어에서 메모리가 어떻게 관리되는지 가비지 콜렉션이 언제 수행되는지 그리고 가비지 콜렉션이 수행될 때 거치는 두가지 과정에 대해 알아봤다. 이러한 밑 바탕이 되는 지식과 함께 본격적으로 플렉스 프로파일링을 시작해 보겠다.


3. Flex Profiling perspective
Flex Profiling perspective 는 플렉스빌더에서 제공하는 프로파일링 관련 도구들을 모아놓은 작업 환경이다.
이 작업 환경에서 실시간으로 애플리케이션이 사용하는 메모리를 비롯해 생성된 객체들의 수를 확인할 수 있는 메모리 프로파일링과, 함수 수행 시간을 확인할 수 있는 퍼포먼스 프로파일링을 할 수 있다.


3-1. 메모리 프로파일링

메모리 프로파일링은 일반적으로 애플리케이션의 메모리가 계속 증가 할 때 이용한다.
메모리가 계속 증가하는 이유는 더 이상 사용되지 않는 객체들이 어디선가 참조되고 있어서 가비지 콜렉터가 제거하지 못하기 때문이다. 이러한 현상을 보다 전문적인 용어로 메모리 누수(memory leak) 라고 부른다.

메모리 프로파일링은 메모리 누수를 찾을수 있을뿐만 아니라, 어디서 어떤 객체가 생성되고 있으며 객체들이 얼마 만큼의 메모리를 차지하고 있는지 알 수 있다. 또한 특정한 두개의 서로 다른 시점의 메모리 상태를 저장하고 이를 비교분석 할 수도 있다. 

먼저 정상적인 애플리케이션을 통해 플렉스빌더의 프로파일링 도구를 살펴보자.
아래의 코드를 프로파일링 모드로 실행한다. (디버그 실행버튼 바로 오른쪽 버튼)



프로파일링 실행버튼을 누르면 다음과 같은 창이 나타난다.



위 창은 프로파일링 옵션을 묻는 창이다. 메모리 프로파일링의 경우 두가지 옵션이 더 존재하는데 Watch live memory data는 실시간으로 현재 활동중인 객체와 메모리 크기를 감지하고 지금까지 생성되었던 객체와 메모리의 크기를 알 수 있는 옵션이며, Generate object allocation stack traces는 특정한 두 시점 사이에서 호출된 메소드와 그 메소드에서 사용했던 메모리 크기를 알 수 있는 옵션이다. 모든 체크박스를 선택하게 될 경우 프로파일링 데이터 수집을 그 만큼 더 많이 하게 되기 때문에 애플리케이션의 속도가 느려진다. 지금은 위 그림처럼 메모리 프로파링일 항목만(하위 두개의 옵션 포함) 체크하고 Resume 버튼으로 프로파일링을 시작해보자.

프로파일링이 시작되면 플렉스빌더는 프로파일링 모드로 전환되고 다음 그림처럼 보일 것이다.



①번 영역은 프로파일링 목록과 프로파일링 실행을 제어할 수 있는 [Profile 뷰] 이다.
②번 영역은 현재 메모리 상황을 그래프로 표현하는 [Memory Usage 뷰] 이다.
③번 영역은 실시간으로 메모리에 존재하는 객체들을 볼 수 있는 [Live Objects 뷰] 이다.

그럼 먼저 프로파일링을 시작했을 때 기본적으로 보여지는 위 세가지 뷰에 대해 자세히 알아보겠다.


3-1-1. Profile 뷰
프로파일링이 시작된 이후에, 프로파일링을 일시중지 하고 다시 시작 하는 등 실행을 제어 하거나, 메모리와 퍼포먼스 분석을 위한 스냅샷 저장을 할 때 [Profile 뷰] 이용한다. [Profile 뷰] 는 프로파일링 실행을 제어할 수 있는 컨트롤 부분과 프로파일링이 진행중 이거나 이미 중단된 애플리케이션과 스냅샷의 목록이 나타나는 부분이 있다. 애플리케이션 목록 중 하나를 선택하고 수행하길 원하는 버튼을 클릭함으로써 프로파일링을 제어하게 된다. 그럼 실행을 제어하는 컨트롤 부분의 버튼들이 어떤 기능을 하는지 알아 보도록 하겠다.

 Resume
일시 정지된 애플리케이션과 함께 프로파일링을 다시 계속한다.

 Suspend
애플리케이션과 함께 프로파일링을 일시 정지한다.

 Terminate
프로파일링을 종료 한다. 애플리케이션(브라우저)는 종료되지 않는다.

 Run Garbage Collector
강제적으로 가비지 콜렉션 수행한다. 이 버튼을 누르면 [Memory Usage 뷰] 에서 가비지 콜렉션이 수행된 것을 알 수 있다.

 Take Memory Snapshot
현재 실행중인 애플리케이션의 메모리 상황을 저장하고, [Profile 뷰]의 선택된 애플리케이션 하위로 메모리 스냅샷 항목이 추가된다. 이 버튼을 누르면 가장 먼저 가비지 콜렉션을 수행한 뒤 메모리 상황을 저장한다. 가비지 콜렉션이 먼저 수행되는 이유는 가비지 객체들이 메모리 상황을 분석하는데 있어서 잘못된 정보로 활용되기 때문이다. 메모리 스냅샷은 1개 이상 저장할 수 있고, 저장된 스냅샷 목록중 하나를 더블클릭 했을 때 열리는 [Memory Snapshot 뷰] 를 이용해 그 당시 메모리 상황을 분석할 수 있다. 또한 저장된 스냅샷 목록 중 두개의 스냅샷을 한 쌍으로하여 앞으로 나오게 될 두 시점 사이의 비교 기능을 이용할 수 있다. 자세한 내용은 
3-1-4. Memory Snapshot 뷰, 3-1-6. Loitering Objects 뷰, 3-1-7. Allocation Trace 뷰 이 세 파트에 걸쳐서 다루겠다.

 Find Loitering Objects
이 버튼은 메모리 스냅샷 목록 중 두개를 선택 했을 때 작동 된다. 이 버튼은 선택된 두 메모리 스냅샷을 비교하여 두 시점 사이에서 생성된 객체들을 확인할 수 있는 [Loitering Objects 뷰]를 연다. 자세한 내용은 
3-1-6. 
Loitering Objects 뷰
 파트에서 다루겠다.

 View Allocation Trace
이 버튼은 프로파일링을 시작할 대 실행 옵션을 묻는 창에서 Generate object allocation stack traces 항목을 체크하고, 메모리 스냅샷 목록 중 두개를 선택 했을 때 작동 된다. 이 버튼은 선택된 두 메모리 스냅샷을 비교하여 두 시점 사이에서 수행된 메소드들을 확인할 수 있는 [Allocation Trace 뷰]를 연다. 자세한 내용   3-1-7. Allocation Trace 뷰 파트에서 다루겠다.

 Reset Performance Data
현재까지 플렉스빌더가 수집한 퍼포먼스 데이터를 초기화 한다. 플렉스빌더는 이 버튼이 눌린 시점으로 하여 퍼포먼스 데이터를 새로 수집하게 된다.

 Capture Performance Profile
프로파일링이 시작된 이후 또는 퍼포먼스 데이터가 초기화된 시점부터 현재까지 수집한 퍼포먼스 데이터를 저장하고, [Profile 뷰]의 선택된 애플리케이션 하위로 퍼포먼스 스냅샷 항목을 추가한다. 추가된 퍼포먼스 스냅샷 항목을 더블클릭 하면 성능에 관련된 정보를 보여주는 [Performance Profile 뷰]가 열린다. 자세한 내용은 3-2. 퍼포먼스 프로파일링 파트에서 다루겠다.

 Delete
선택된 스냅샷 항목 또는 프로파일링이 종료되고 선택된 애플리케이션을 목록에서 제거한다.


3-1-2. Memory Usage 뷰
만약 여러분의 애플리케이션에서 메모리가 정상적인지 확인하고 싶다면 가장 먼저 [Memory Usage 뷰]를 확인하라. [Memory Usage 뷰]를 통해 플래시 플레이어의 메모리 상황을 한눈에 파악할 수 있기 때문에 메모리 누수가 발생하는지 가장 쉽고 빠르게 알 수 있는 도구이다.



위 그림에서 빨간색 라인은 애플리케이션이 시작된 이후로 가장 많은 메모리를 사용했던 양을 나타내고, 파란색 라인은 현재 사용중인 메모리 양을 나타낸다. 위 그래프는 매우 정상적인 흐름의 톱니 패턴을 띄고 있다. 이 톱니 패턴은 앞서 설명 했듯이 애플리케이션이 플래시 플레이어가 OS로 부터 빌려온 메모리 보다 더 많은 메모리를 요구할 때 가비지 콜렉션이 수행된 흔적이다.


3-1-3. Live Objects 뷰
애플리케이션에 존재하는 객체들을 실시간으로 확인하고 싶을 때 [Live Objects 뷰]를 이용한다. 이 뷰는 프로파일링이 진행되는 동안 애플리케이션에서 실시간으로 생성되는 객체와 그 객체가 얼마나 많은 메모리를 사용하는지 보여준다. 또한, 그것들에 대한 누적된 수치도 함께 보여준다.



위 그림에서 알 수 있듯이 [Live Objects 뷰]에는 6개의 컬럼이 존재한다. 이 중 Class, Package 컬럼은 궂이 설명하지 않아도 어떤 컬럼인지 알 수 있을 것이다. 그 두개를 제외한 나머지 컬럼들에 대해 알아보자.

Cumulative Instances
프로파일링이 시작된 이후로 생성된 객체의 누적 합계이다.

Instances
현재 메모리에 존재하는 객체의 수이다.

Cumulative Memory
프로파일링이 시작된 이후로 생성된 객체가 사용했던 메모리 양의 누적 합계이다.

Memory
현재 메모리에 존재하는 객체가 사용중인 메모리 양이다.


위 그림에서 처럼 Package 컬럼 값이 없거나 그 외에 생소한 값을 가진 항목들은 우리가 만든 애플리케이션 단계의 정보가 아니기 때문에 그다지 유용한 정보가 되지 못한다. 지금 우리에게 필요한 정보는 예제 코드에서 1초마다 100개씩 생성되고 있는 DataGrid 객체에 대한 정보일 것이다. 하지만 기본적으로 적용된 필터 때문에 우리에게 필요한 DataGrid 객체에 대한 정보를 볼 수 없기 때문에 필터를 수정해야 한다. 

먼저 위 그림의 우측 상단의 빨간색 마크된 부분의 세번째 아이콘을 누르면 Filters 창이 나타나는데, Inclusion filters와 Exclustion filters를 다음과 같이 변경하자. 그러면 필터의 포함 항목과 제외 항목을 통해 적절하게 필터링된 객체들이 보여질 것이다.



DataGrid 클래스의 패키지인 mx.controls.* 패키지를 포함 시켰다면 다음과 같이 [Live Objects 뷰]에 DataGrid 하나만 나타날 것이다.



위 그림에서 처럼 DataGrid 객체가 지금까지 어마어마하게 생성 되었으며, 현재 메모리에서 존재하는 객체의 수를 비롯해 사용중인 메모리 양도 쉽게 알 수 있다. 이처럼 [Live Objects 뷰]를 통해 실시간으로 생성되거나 소멸되는 객체들 뿐만 아니라, 누적된 수치까지 확인함으로써 애플리케이션의 전반적인 메모리 상황을 알 수 있고, 이러한 정보는 여러분의 애플리케이션을 깊게 이해하는데 도움이 될 것이다.


※코드 변경하기
지금까지 프로파일링이 시작되었을 때 구성되는 기본적인 뷰들에 대해 알아봤다. 이제 예제 코드를 변경한 후 [Memory Usage 뷰]를 통해 메모리 누수를 확인할 것이다. 그리고 앞으로 나오게 될 [Memory Snapshot 뷰] 와 [Loitering Objects 뷰] 그리고 [Allocation Trace 뷰]를 이용해 어디에서 메모리 누수가 발생하는지 확인할 것이다. 변경된 코드는 다음과 같다.


코드를 변경했다면 프로파일링을 시작하고 [Memory Usage 뷰]를 관찰해 보자. 다음 그림처럼 메모리가 계속 상승하는 그래프를 볼 수 있을 것이다. 이렇게 메모리가 계속 상승하는 이유는 가비지 콜렉션이 수행될 때 메모리에서 제거 되어야할 객체들이 어디선가 계속 참조되고 있기 때문이다.



변경된 예제코드에서 직접 메모리 누수 부분을 찾을 수 있지만 우리는 그 사실을 모른다고 가정하고 메모리 프로파일링을 통해 어디에서 메모리 누수가 발생하는데 찾아볼 것이다.

3-1-4. Memory Snapshot 뷰
여러분이 과거 특정한 시점에 저장했던 메모리 상황을 알고 싶을 때 [Memory Snapshot 뷰]를 이용한다. 이 뷰는 특정한 시점의 메모리 상황을 보여 주는 것이기 때문에 [Live Objects 뷰] 처럼 메모리 상황을 업데이트 하지 않고, 오직 해당 시점의 메모리 상황만 보여주는 정적인 뷰이다. [Profile 뷰]에서 저장된 메모리 스냅샷 항목을 더블클릭 하면 이 뷰를 열 수 있다.

이제 위 예제코드의 문제점을 찾아보자. [Memory Usage 뷰] 에서 메모리 누수를 포착 했다면 가장 먼저 [Live Objects 뷰]를 통해서 어떤 객체가 메모리에서 계속 증가하는지 발견해야 한다. 여러분은 이미 [Live Objects 뷰]에 대한 사용법을 알고 있고, 직접 확인해보면 DataGrid 객체가 계속 증가하는 것을 확인할 수 있을 것이다. 확인 했다면 Take Memory Snapshot 버튼으로 메모리 스냅샷을 만들고, 추가된 메모리 스냅샷을 더블클릭 하여 [Memory Snapshot 뷰]를 열어보자. [Memory Snapshot 뷰]를 열었다면 다음 그림처럼 DataGrid 객체만 나오도록 앞서 배운 필터를 설정해보자.



[Memory Snapshot 뷰]에서 제공하는 컬럼들은 앞서 배운 [Live Objects 뷰]에 이미 있는 것들이다. 우리는 현재까지 [Memory Usage 뷰]를 통해 메모리 누수를 발견하고 [Live Objects 뷰]를 통해 DataGrid 가 계속 생성되는 것을 확인했다. 이제는 그 문제의 DataGrid 객체가 과연 어디에서 생성되는지 알아야할 때 이다. 위 그림에서 알 수 있듯이 [Memory Snapshot 뷰] 자체만으로는 DataGrid가 어디서 생성되는지 알 수가 없다. 하지만 이 뷰에서 특정 로우를 더블클릭 하거나, 특정 로우를 선택한 상태에서 우측상단 빨간색 박스 부분의 첫번째 아이콘을 누르면 객체가 어디에서 생성되었는지 알려주는 [Object References 뷰]를 열 수 있다. 자세한 내용은 바로 다음 3-1-5. Object References 뷰 파트에서 다룬다.

3-1-5. Object References 뷰
메모리 프로파일링을 하는 동안 객체가 생성된 위치를 확인 하고자 할 때 [Object References 뷰]를 이용한다. 이 뷰는 객체를 생성시킨 메소드를 보여주며 해당 메소드의 소스코드 위치까지 알려준다.

우리는 이 뷰를 [Memory Snapshot]뷰를 통해 연다는 것을 바로 이전 파트에서 배웠다. 이제 문제의 DataGrid 객체가 어디에서 생성되는지 확인하기 위해 이 뷰를 열어보자.



이 뷰는 객체의 리스트를 보여주는 [Instance] 영역과 해당 객체가 생성된 위치를 보여주는 [Allocation Trace] 영역으로 나뉘어져 있다. 위 그림에서 알 수 있듯이 좌측 [Instance] 영역에서 무수히 많이 생성된 DataGrid 객체를 선택하면 우측 [Allocation Trace]에서 그 객체를 생성한 메소드와 소스 위치까지 파악할 수 있다. 이러한 정보를 이용해 잘못된 애플리케이션을 즉시 수정할 수 있을 것이다.


3-1-6. Loitering Objects 뷰
과거 특정한 시점에 메모리 스냅샷 두개를 저장했고 그 두 시점 사이에서 생성 되었던 객체들을 확인하고 싶을 때 [Loitering Objects 뷰]를 이용한다. 이 뷰는 두 시점 사이에서 생성된 객체와, 그 객체가 사용했던 메모리의 총 합계를 보여준다.

이제 이 뷰를 활용하기 위해 프로파일링을 다시 시작하고, 시작 하자마자 메모리 스냅샷을 저장하고 약 10초뒤 다시 한번 메모리 스냅샷을 저장 해보자. 그리고 [Profile 뷰]에서 두 개의 메모리 스냅샷을 선택하고 Find Loitering Objects 버튼을 누르면 다음 그림처럼 [Loitering Objects 뷰]가 나타날 것이다.



위 그림에서 짧은 시간에 DataGrid 객체가 900개나 생성되고 전체 메모리에서 62%에 해당하는 메모리를 사용하고 있음을 알 수 있다. 이 처럼 이 뷰는 메모리 누수를 발견 하는데 유용하며 [Memory Sanapshot 뷰]와 마찬가지로 특정 객체를 더블클릭 하거나 우측 상단의 빨간색으로 마킹된 아이콘을 누르면 해당 객체가 생성된 위치를 보여주는 [Object References 뷰]를 열 수 있다.


3-1-7. Allocation Trace 뷰
[Allocation Trace 뷰]는 바로 이전 파트에서 배웠던 [Loitering Objects 뷰]와 비슷한데 특정한 두 시점 사이의 메모리 상황을 분석할 때, 호출된 메소드에 대한 정보를 알고 싶을 때 이용한다. 앞서 말했듯이 [Loitering Objects 뷰]가 생성 되었던 객체를 보여줬다면, 이 뷰는 호출된 메소드와 그 메소드가 호출되는 동안 생성한 객체의 수를 비롯하여, 사용했던 메모리 양을 보여준다. 

이제 [Profile 뷰]에서 3-1-6 Loitering Object 뷰 파트에서 저장했던 두 개의 메모리 스냅샷을 선택하고, View Allocation Trace 버튼을 누르면 다음 그림처럼 [Allocation Trace 뷰]가 나타날 것이다.



위 그림과 같이 [Allocation Trace 뷰]에는 6개의 컬럼이 존재한다. 이 중 Method, Package 컬럼은 궂이 설명하지 않아도 어떤 컬럼인지 알 수 있을 것이다. 그 두개를 제외한 나머지 컬럼들에 대해 알아보자.

Cumulative Instances
두 시점 사이에서 이 메소드에 의해서 호출된 메소드와 이 메소드 자체에서 생성된 객체의 수이다.

Self Instances
두 시점 사이에서 이 메소드 자체에서 생성된 객체의 수이다.

Cumulative Memory
두 시점 사이에서 이 메소드에 의해서 호출된 메소드와 이 메소드 자체에서 생성된 객체가 사용한 메모리 양이다.

Self Memory
두 시점 사이에서 이 메소드 자체에서 생성된 객체가 사용한 메모리 양이다.


[Allocation Trace 뷰]를 통해서 짧은 시간동안 timerHandler 라는 메소드에서 많은 객체를 생성하고 많은 메모리를 사용한다는 것을 알 수 있다. 위 그림에서 처럼 timerHandler 라는 메소드가 과연 어떠한 객체를 생성했는지는 알 수 없다. 그러나 해당 메소드를 더블클릭 하거나 우측 상단의 빨간색 마킹된 버튼을 누르면 어떤 객체가 생성 됐는지 보여주는 [Object Statistics 뷰]를 열 수 있다. 바로 다음 파트에서 이 뷰에 대해 자세히 알아보겠다.

3-1-8. Object Statistics 뷰
과거 특정한 두 시점 사이에서 호출 되었던 메소드에 대한 메모리 통계정보를 알고 싶을 때 [Object Statistics 뷰]를 이용한다. 이 뷰는 해당 메소드가 수행 되는 동안 생성한 객체가 무엇인지, 그 객체가 얼마나 많은 메모리를 사용했는지 보여준다. 

이 뷰는 바로 이전 파트에서 [Allocation Trace 뷰]를 통해 연다는 것을 배웠다. 이제 특정 메소드의 통계 정보를 보기 위해 이 뷰를 열어보자.



위 그림처럼 이 뷰는 크게 3가지 영역으로 구분할 수 있다.
번 영역은 호출된 메소드에 대한 요약된 정보로서 [Allocation Trace 뷰]에서 보여지던 정보와 동일하다.
번 영역은 이 메소드 자체에서 생성된 객체와 그 객체가 사용하는 메모리 양을 보여준다.
번 영역은 이 메소드에 의해 호출된 다른 메소드에서 생성던 객체와 그 객체가 사용하는 메모리 양을 보여준다.
이 처럼 [Object Statistics 뷰]는 이제껏 봐왔던 뷰들에 대한 전체적인 정보를 보여주는 통계뷰 라고 할 수 있다. 이러한 통계정보를 바탕으로 보다 종합적인 메모리 분석을 할 수 있을 것이다.

우리는 지금 까지 플렉스빌더에서 메모리 프로파일링을 통해 메모리 누수 감지와 원인 발견 및 애플리케이션 전체적인 메모리 상황에 대한 정보를 알 수 있었다. 이런 정보를 안다면 애플리케이션 설계에 있어서도 도움이 될 것이다. 이제 메모리 상황이 아닌 성능에 초점을 둔 퍼포먼스 프로파일링에 대해 알아 보도록 하자.


3-2. 퍼포먼스 프로파일링
퍼포먼스 프로파일링은 애플리케이션에서 응답이 느린 메소드를 찾아내거나, 성능이 향상될 수 있는 메소드를 찾아 내고자 할 때 이용한다. 퍼포먼스 프로파일링은 이 두가지 타입의 메소드를 최적화 하기 위한 리팩토링 정보를 제공하여 애플리케이션의 전체적인 성능을 향상 시킬 수 있다.

우리는 잘못된 예제코드를 이용하여 수행시간 가장 긴 메소드와 빈번히 호출되는 메소드를 찾아낼 것이다. 예제코드는 다음과 같다.



위 예제코드를 다 적었다면 프로파일링을 시작해보자. 이번에는 퍼포먼스 프로파일링만 하기 때문에 실행 옵션을 묻는 창에서 가장 아래 있는 Enable performance profiling 항목에만 체크를 한다. 그리고 나서 애플리케이션이 시작되면 화면에 보이는 버튼을 10회 누른다. 플렉스빌더는 프로파일링이 시작된 이후로 성능 데이터를 수집 하는데, 성능 데이터는 메소드 호출시간과 횟수이다. 따라서 여러분이 버튼을 10회 눌렀다면 버튼에 의해 실행된 메소드 정보가 수집 되었을 것이다. 이제 플렉스빌더로 돌아가서 [Profile 뷰]에 있는 
Capture Performance Profile 아이콘을 누르면, 다음 그림처럼 플렉스빌더가 아이콘을 누른 시점까지 수집했던 성능 데이터를 저장하게 된다.



퍼포먼스 데이터가 저장되면 위 그림처럼 [Profile 뷰]에 목록으로 추가된다. 또한 하나 이상의 퍼포먼스 데이터를 저장할 수 있으며, 플렉스빌더가 수집한 퍼포먼스 데이터를 초기화 하고 싶다면 
Reset Performance Data 아이콘을 누르면 된다. 저장된 목록중 하나를 더블클릭하면 퍼포먼스 데이터를 볼 수 있는 [Performance Profile 뷰]가 열린다. 다음 파트에서 [Performance Profile 뷰]를 통해 수집된 퍼포먼스 데이터를 확인해 보도록 하겠다.

3-2-1. Performance Profile 뷰
[Performance Profile 뷰]는 퍼포먼스 프로파일링을 하는 동안 사용하는 가장 중요한 뷰 이다. 이 뷰는 메소드 호출에 관련된 통계를 보여주며, 이러한 정보는 애플리케이션의 병목현상을 개선하거나 수행속도를 높이는 정보로 이용될 수 있다.

[Profile 뷰]에서 저장된 퍼포먼스 데이터 항목을 더블클릭 하면 다음 그림처럼 [Performance Profile 뷰]가 열릴 것이다.


위 그림과 같이 [Performance Profile 뷰]에는 6개의 컬럼이 존재한다. 이 중 Method, Package 컬럼은 궂이 설명하지 않아도 어떤 컬럼인지 알 수 있을 것이다. 그 두개를 제외한 나머지 컬럼들에 대해 알아보자.

Calls
퍼포먼스 데이터가 저장된 시점까지 메소드가 호출된 횟수이다.

Cumulative Time

이 메소드에 의해서 호출된 메소드들의 총 수행 시간이다.

Self Time
이 메소드 자체의 수행 시간이다.

AVG. Cumulative Time
이 메소드에 의해서 호출된 메소드들의 평균 수행 시간이다.

AVG. Self Time
이 메소드 자체의 평균 수행 시간이다.


위 그림은 Cumulative Time 컬럼을 눌러서 내림차순 정렬한 것 이다. 정렬을 통해서 가장 응답이 느린 메소드를 확인해 본 결과, 자체적인 수행시간이 가장 느린 메소드는 slow() 임을 확인할 수 있다. 이제 여러분이 직접Calls 컬럼으로 정렬을 하면 가장 많이 호출된 메소드는 fast() 임을 확인할 수 있을 것이다. 이제 여러분은 메소드의 수행 시간을 알게 되었으므로 리팩토링을 통해 성능 향상을 꽤할 수 있을 것이다.

또한 이 뷰에서 특정 메소드 항목을 더블클릭 하거나 위 그림의 우측상단 빨간색 마크된 아이콘을 누르면 해당 메소드의 좀 더 진보된 정보를 볼 수 있는 [Method Statistics 뷰]를 열 수 있다. 다음 파트에서 [Method Statistics 뷰]에 대해 자세히 알아 보겠다.

3-2-2. Method Statistics 뷰
퍼포먼스 프로파일링 하는동안 호출된 메소드에 대한 성능 통계정보를 알고 싶을 때 [Method Statistics 뷰]를 이용한다. 이 뷰는 호출된 메소드에 대한 수행시간과 더불어 메소드 내에서 호출된 다른 메소드나, 자신이 어디서 호출되었는지 정보를 제공한다. 

이제 
[Performance Profile 뷰]에서 Cumulative Time 이 가장 오래 걸린 execute() 항목을 더블클릭해서 이 뷰를 열어보자.



이 뷰는 크게 3가지 영역으로 구분할 수 있다.
번 영역은 이 메소드 자체 수행시간으로써 [Performance Profile 뷰]에서 보여지던 정보와 동일하다.
번 영역은 이 메소드를 호출한 메소드에 대한 수행 시간을 보여준다.
번 영역은 이 메소드에 의해 호출된 다른 메소드들의 수행 시간을 보여준다.
이 처럼 [Method Statistics 뷰]는 수행시간과 더불어 메소드들의 흐름과 같은 최적화 하기 유용한 정보를 제공한다. 애플리케이션의 성능과 흐름을 파악하는 것 만으로도 앞으로 여러분이 무엇을 해야할지 알게 될 것이다.


4. 맺음말
지금까지 프로파일링에 대해서 알아봤다. 이 글에서 소개한 방법으로 메모리와 퍼포먼스를 측정하여 여러분의 애플리케이션의 성능을 향상시켜 보도록 하자. 끝으로 프로파일링 또한 디버깅과 마찬가지로 개발이 끝난 후가 아니라 개발 과정의 일부분으로 행해 진다면 좋은 품질의 애플리케이션을 만들 수 있을 것이다.


출처 : http://jjaeko.tistory.com/104

Posted by 1010
00.Flex,Flash,ActionScript2014. 8. 20. 14:26
반응형
참조 : Adobe Flex3 실전 트레이닝 북, 국내 블로그, 해외 블로그
이 글은 어도비리아에 기술문서로 제출 되었으며, 동일한 내용을 어도비리아에서 볼 수 있습니다.



-1부 디버깅-
<목차>
1. 디버깅이란?
2. 디버깅의 종류
2-1. Trace()
2-2. <mx:TraceTarget />
2-3. Flex Debugging perspective
2-3-1. Debug view
2-3-2. Variables view
2-3-3. Expressions view
2-3-4. Breakpoints view
2-4. 
flash.debugger.enterDebugger()
3. 맺음말


1. 디버깅이란?
프로그래머가 의도하지 않은 소프트웨어의 오작동을 버그라고 한다.
디버깅은 이러한 버그를 찾아내고 수정하는 것을 말하며,
소프트웨어의 품질을 향상 시키기위해 반드시 거쳐야 할 과정이다.

이 글에서 여러분은 Flex Builder에서 효과적으로 디버깅 하는 방법을 배울 것이며,
더 나아가 개발 습관에도 좋은 영향을 줄 것이다.


2. 디버깅의 종류
Flex에서 할 수 있는 디버깅은 크게 3가지가 있다.

- Trace()
- <mx:TraceTarget />
- Flex Debugging perspective

이 3가지 방법 모두 "특정한 시점에 객체가 가지고 있는 값" 을 확인하는 것에 초점을 둔다.
그럼으로써 프로그램이 의도한대로 동작하는지 알게 될 것이다.
그리고 이 중에서 Trace()와 <mx:TraceTarget /> 태그는 디버깅 보다는 로깅에 가깝다.
하지만 "특정한 시점에 객체가 가지고 있는 값" 을 확인하는 용도로써 훌륭한 디버깅 도구가 될 수 있다.

그럼 이제부터 이 3가지 종류에 대해 알아보도록 하겠다.

2-1. Trace()

프로그램의 특정 기능이 수행되는 동안 객체들이 의도한 값을 가지고 있는지 확인하고 싶을때 
이 함수를 이용한다. 이 함수는 객체의 값을 콘솔에 출력해주는 역할을 한다.
프로그램의 흐름속에 Trace() 함수를 이용해 값을 확인 함으로써 프로그램이 정상적으로 작동 되는지 
확인할 수 있을 것이다.

간단한 예제를 통해 사용법을 알아 보겠다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="creationComp()">
    <mx:Script>
        <![CDATA[
            import mx.utils.ObjectUtil;
            private function creationComp():void {
                var obj:Object = new Object();
                obj.a = "a";
                obj.b = "b";
                
                trace(ObjectUtil.toString(obj));
            }
        ]]>
    </mx:Script>
</mx:Application>


위 코드를 디버그 모드로 실행(F11 키 또는 벌레모양 아이콘
) 하면 콘솔에서 다음과 같은 결과를 확인할 수 있다.

(Object)#0
  a = "a"
  b = "b"


위 예제에서 사용한 ObjectUtil 클래스는 Trace() 함수와 함께 사용하면 매우 유용하다.
ObjectUtil.toString() 메서드는 객체가 가지고 있는 프로퍼티와 값을 출력결과 처럼 포맷해서 리턴해준다.

2-2. <mx:TraceTarget />
Flex 애플리케이션을 개발 하는데 있어 어려운 부분 중 하나는 서버와 통신하는 경우이다.
어떤 원인에 의해 서버로부터 기대했던 데이터를 받지 못할 경우 원인을 찾아내기가 힘들다.
이런 경우 <mx:TraceTarget /> 태그를 이용하면 원인을 쉽게 파악할 수 있다.
이 태그는 서버와 통신을 할 때 통신상태 및 전송되는 데이터를 콘솔에 출력해주는 역할을 한다.
단지 태그를 MXML 문서에 추가하기만 하면 콘솔에서 그 내용을 확인할 수 있다.

간단한 예제를 통해 사용법을 알아 보겠다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">    

    <mx:TraceTarget />
    
    <mx:HTTPService id="http" 
                               url="http://www.naver.com" 
                               resultFormat="text" />
                    
    <mx:Button  label="click me"
                      click="http.send()" />
                                
</mx:Application>


위 코드를 디버그 모드로 실행(F11 키 또는 벌레모양 아이콘) 하면 콘솔에서 다음과 같은 결과를 확인할 수 있다.

mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer set destination to 'DefaultHTTP'.
mx.messaging.Channel 'direct_http_channel' channel endpoint set to http:
mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer sending message '54738616-39F6-E6DA-940C-A2BA98DFACB2'
mx.messaging.Channel 'direct_http_channel' channel sending message:
(mx.messaging.messages::HTTPRequestMessage)#0
  body = (Object)#1
  clientId = (null)
  contentType = "application/x-www-form-urlencoded"
  destination = "DefaultHTTP"
  headers = (Object)#2
  httpHeaders = (Object)#3
  messageId = "54738616-39F6-E6DA-940C-A2BA98DFACB2"
  method = "GET"
  recordHeaders = false
  timestamp = 0
  timeToLive = 0
  url = "http://www.naver.com"
mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer connected.
mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer acknowledge of '54738616-39F6-E6DA-940C-A2BA98DFACB2'.
mx.rpc.http.HTTPService Decoding HTTPService response
mx.rpc.http.HTTPService Processing HTTPService response message:
(mx.messaging.messages::AcknowledgeMessage)#0
  body = "결과 값 생략"
  clientId = "DirectHTTPChannel0"
  correlationId = "54738616-39F6-E6DA-940C-A2BA98DFACB2"
  destination = ""
  headers = (Object)#1
    DSStatusCode = 200
  messageId = "EC0ED47D-ABD2-8671-79ED-A2BA9950521B"
  timestamp = 0
  timeToLive = 0


위 결과에서 알 수 있듯이 전송상태 및 전송 데이터("결과 값 생략" 부분)를 쉽게 확인할 수 있다.
또한 이 태그는 디버깅 보다는 로깅과 매우 밀접한 연관이 있다.
하지만 이 글에서는 오직 디버깅의 용도로만 사용한 것이며 로깅에 관한 부분은 다루지 않는다.


2-3. Flex Debugging perspective
Flex Builder(Eclipse Platform, 이하 빌더) 에서 지원하는 디버깅으로써 이 글의 핵심이라고 할 수 있다.
개발자가 원하는 "특정한 시점"에 객체들의 상세한 값을 확인하고자 할 때 이 기능을 이용한다.
빌더에서 지원하는 디버깅 기능을 이용하면 애플리케이션을 일시 중지하고 중지된 상태의 시점에서
객체들의 상태를 확인할 수 있을 뿐만 아니라 한줄한줄 코드 실행을 제어할 수 있다.

그럼 실습을 통해 자세하게 알아보겠다.

빌더에서 지원하는 디버깅은 "특정한 시점" 으로부터 시작된다.
따라서 여러분은 먼저 객체의 값을 확인하고자 하는 "특정한 시점"을 정해야 한다.
먼저 소스뷰에서 특정 라인번호를 더블클릭 하면 다음 그림처럼 빨간색 박스친 부분의 점이 표시될 것이다.
그 점이 바로 "특정한 시점" 이며 애플리케이션이 중지되고 객체의 상태를 확인할 수 있는 지점이다.
빌더에서는 "특정한 시점"을  보다 전문적인 용어로 "브레이크 포인트" 라고 부른다.



이제 애플리케이션을 디버그 모드
(F11 키 또는 벌레모양 아이콘) 로 실행 시켜보자.
애플리케이션이 시작되면 초기화 과정(creationComplete 이벤트)에서 브레이크 포인트 지점의 코드가 
수행된다. 
브레이크 포인트 지점의 코드가 수행될 때 Flex Builder는 자동으로 디버깅 모드로 전환되고
애플리케이션은 정지상태가 된다. 

디버깅 모드로 전환된 모습은 다음 그림과 같다.



①번 영역은 코드 실행을 제어하거나 세부적인 디버깅을 컨트롤할 때 사용하는 Debug view로 구성되어 있다.
②번 영역은 객체의 상세한 정보를 볼 수 있는 Variables view, 
브레이크 포인트의 목록을 확인할 수 있는 Breakpoints view, 
특정 변수를 주시하고 싶을때 사용하는 Expressions view 로 구성되어 있다.

그럼 이제부터 각각의 view에 대해 자세히 알아보겠다.

2-3-1. Debug view
애플리케이션이 브레이크 포인트를 만나서 디버깅 모드로 진입했을 때 애플리케이션을 디버깅을 중지하고 다시 실행시키거나 한줄한줄 코드를 실행하고자 할 때 Debug view를 사용한다.
Debug view에는 다음과 같이 디버깅을 제어할 수 있는 아이콘이 있다.

Resume

브레이크 포인트에 의해 애플리케이션이 정지상태가 되었을 때, 디버깅을 멈추고 정지된 애플리케이션을 이어서 가동시킨다. 만약 또 다른 브레이크 포인트를 만난다면 다시 디버깅 모드로 진입된다.

Suspend

애플리케이션과 함께 디버깅을 일시 중지한다.

Terminate

애플리케이션과 함께 디버깅을 종료한다.

Disconnect

Flex Builder의 디버거와 연결을 끊는다.
연결이 끊기면 브레이크 포인트를 만나더라도 디버깅 모드로 진입할 수 없다.

Step Into

현재 라인이 함수라면 함수 내부의 첫번째 라인으로 이동한다.
만약 현재 라인이 함수가 아니라면 현재 라인을 실행하고 다음 라인으로 이동한다.

Step Over

현재 라인을 실행하고 다음 라인으로 이동한다.

Step Return

현재 실행중인 함수를 호출한 부모함수로 이동한다.
예를들면, Step Into에서 함수 내부로 이동했을 경우 다시 밖으로 빠져 나오게 된다.


2-3-2. Variables view
애플리케이션이 브레이크 포인트를 만나 정지상태가 되었을 경우 정지된 시점에서의 객체들의 값을 확인할 때 Variables view를 이용한다.
다음 그림은 이번 장의 예제코드를 디버그 모드로 실행했을 때의 Variables view의 모습이다.


위 그림에서 this는 현재 브레이크 포인트가 위치한 클래스(또는 MXML문서) 이다.
트리의 노드 앞에 있는 아이콘은 해당 노드의 가시성을 나타낸다.
따라서 this 하위에 있는 a는 private, b는 protected, c는 public 가시성을 가지는 변수이다.
그리고 회색(L) 아이콘의 obj는 현재 수행중인 함수의 로컬변수임을 뜻한다.

this의 트리를 계속 열면 수백 개의 변수를 확인할 수 있으며, Ctrl+F 키나 마우스 우클릭후 Find Variable 메뉴를 통해 다음 그림처럼 특정 변수를 검색할 수 있다.


위 그림처럼 변수명을 입력하면 실시간으로 원하는 변수를 찾을 수 있다.


2-3-3. Expressions view
Variables view의 수많은 변수 중 특정한 변수를 주시하고 싶다면 Expressions view을 이용하면 된다.
Expressions view 는 특정 변수를 주시할 수 있고 변수들의 값을 원하는 형태의 결과로 볼 수 있는 기능을 제공한다.

먼저 예제 코드를 다음과 같이 변경하고 브레이크 포인트를 함수 종료부분에 지정하자.



코드를 변경 했다면 디버그 모드
(F11 키 또는 벌레모양 아이콘) 로 애플리케이션을 시작한다.
애플리케이션이 브레이크 포인트를 만나 디버깅 모드로 진입하면 Variables view에서  주시하고자 하는 변수에
마우스 우클릭후 Create Watch Expression 메뉴를 클릭한다. 
이제 Expressions view를 확인해보면 다음 그림처럼 선택한 변수가 추가되어 있을 것이다.


이렇게 Expressions view에 변수를 추가해 놓으면 디버깅 하는 동안 Variables 창의 수많은 변수들 중 해당 변수를 찾을 필요 없이 바로 확인할 수 있다.

이번에는 Expressions view를 보다 효과적으로 사용하는 방법에 대해 알아보겠다.
먼저 Expressions 
view에서 마우스 우클릭후 Add Watch Expression 메뉴를 클릭한다.
그리고 다음 그림처럼 변수들을 사칙연산으로 표현해보자.



OK 버튼을 누르고 Expressions 
view를 확인해보면 다음 그림처럼 보일 것이다.



이처럼 Expressions 
view에서는 특정 변수를 주시할 수 있을 뿐만 아니라 변수들의 연산까지 지원해서
변수들의 상호작용에 대한 결과를 바로 알 수 있다.

2-3-4. Breakpoints view
브레이크 포인트를 여기저기 지정하고 해당 문서를 닫았을 경우 지정한 브레이크 포인트들을 찾는 것은 쉬운일이 아니다. 이럴 때 Breakpoints view를 사용하면 지정한 브레이크 포인트들을 쉽게 찾을 수 있다.

다음 그림을 살펴보자.


이처럼 Breakpoints view에는 지정한 브레이크 포인트들의 리스트가 보여진다.
그리고 Delete키 또는 마우스 우클릭후 Remove메뉴를 통해 브레이크 포인트를 바로 제거할 수 있다.

2-4. flash.debugger.enterDebugger()
브레이크 포인트는 빌더에서 관리되기 때문에 브레이크 포인트 자체를 소스코드와 함께 공유할 수는 없다.
그러나 브레이크 포인트와 똑같은 기능을 수행하는 함수가 있다.
바로 flash.debugger.enterDebugger() 함수이다.
이 함수는 브레이크 포인트와 똑같은 기능을 수행하기 때문에 소스코드와 함께 디버깅 지점을 공유할 수 있다.

다음 그림을 보자.


디버깅 지점을 소스코드로 명시했기 때문에 Breakpoints view에 나타나지 않는다.
하지만 그 외에 브레이크 포인트와 다른 점은 없다.


3. 맺음말
지금까지 디버깅에 대해서 알아봤다.
앞으로는 이 글에서 소개한 디버깅 방법으로 소프트웨어 품질을 향상시키는데 노력하자.
마지막으로 디버깅은 개발이 끝난 이후에 하는 것이 아니라 개발과정의 일부분 이라는 것을 명심해야 한다.


출처 : http://jjaeko.tistory.com/103

Posted by 1010
03.HTML 4.X, HTML5, XML...2014. 8. 20. 13:53
반응형

출처 : http://bryan7.tistory.com/202


IE Memory Leak – jQuery Fix


http://kossovsky.net/index.php/2009/07/ie-memory-leak-jquery-garbage-collector/


댓글에 보니 jQuery 를 수정해서 문제를 해결했다는 얘기도 있긴 했지만, 내가 해 봤을 때는 별 효과가 없었다.


IE memory leak


http://epro.tistory.com/6


1. 원인은?

Understanding and Solving Internet Explorer Leak Patterns를 참고해 보면 순환참조 부분이 가장 의심이 되었다.
Ajax를 편하게 쓰기 위해 XMLHttpRequest를 생성해주는 function을 만들어 두고 필요할때 불러서 쓰곤 했는데, 이 부분에서 문제가 있는 듯 했다.
local variable은 function이 종결되면 가비지컬렉터에 의해 삭제되어야 하는데, parameter로 다른 function에 참조되어 있을 경우- 이런 경우를 순환참조라고 하는 것 같다 - 는 leak의 원인이 된다고 한다. (어설픈 해석이다. 대충 이런 뜻인 것 같다)

.... It isn't immediately obvious that parent function parameters and local variables will be frozen in time, referenced, and held until the closure itself is released. .... Because we've added a closure, a second reference is made, and that second reference won't be released until the closure is also released. .....

정확한 내용은 Reference의 Closures부분을 참조할 것
그래서 지역변수로 선언된 XMLHttpRequest를 전역변수로 빼고 비교해 보기로 했다.


Screencast: Diagnosing JavaScript Memory Leaks in IE


http://www.barelyfitz.com/screencast/javascript/memory-leak/


[스크립트] Script 메모리 누수에 대한 TIP


http://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=71441


JavaScript and memory leaks


Memory leaks

http://javascript.info/tutorial/memory-leaks


Understanding and Solving Internet Explorer Leak Patterns

http://msdn.microsoft.com/en-us/library/ms976398.aspx


IE9 이하에서 (IE9 포함) Memory Leak 이 발생하는 경우 -


[2013-12-24]

5초마다 한번씩 반복적으로 ajax로 호출을 하니까 chrome 브라우저에서는 괜찮은데, IE 8, 9 에서는 메모리 증가 현상이 뚜렷했다. 저녁에 웹페이지를 켜 놓은 상태에서 퇴근하고, 아침에 출근해 보면 IE가 먹통이 되었다.

나는 처음에는 ajax 의 반복 호출로 인해 메모리가 조금씩 쌓였을 것이라고 생각했는데, ajax 에서 아무 일도 안 한 상태로 반복 호출해보니 메모리 증가 현상이 없었다. 


ajax 로 서버에서 리스트를 받아서 웹페이지에서 table 태그를 지우고, 다시 써 넣는 작업을 하고 있다.


원인은 <a> 태그 안에 onclick='...' 으로 문자열로 넣은 부분 때문이었다.

IE 9 버전 이하에서는 웹브라우저의 Garbage Collector 의 버그 때문에 이런 경우 onclick 안의 function 의 참조 때문에 화면에서 지워진 태그가 Garbage Collect 되지 않는 것 같다.


Chrome Browser 와 IE 11 브라우저에서는 메모리 증가 현상이 없었다.


해결책은 다음 코드와 같이 onclick='...' 으로 문자열을 적어 넣지 말고, jquery의 bind() 메서드를 사용하면 된다.


  1. // [2013-12-24] Heeseok
  2. // 다음과 같이 하면 IE 9 이하에서 Memory Leak 이 발생하므로 bind 메서드를 써준다.
  3. //td.html("<a href='javascript:" + fnGoDetail + "()' onclick='" + fnGoDetail + "(this);return false;'>" + list[r][col] + "</a>");
  4. td.html("<a href='javascript:" + fnGoDetail + "()'>" + list[r][col] + "</a>");
  5. td.find('a').bind("click", function() {
  6.     eval(fnGoDetail + "(this)");
  7.     return false;
  8. });


IE Process의 Memory 를 30초 단위로 파일로 저장하는 배치 파일


@echo off

SET count=0

SET mem=tasklist /fi ^"imagename eq iexplore.exe^" /NH

echo --------------------------------------------------------------------------- >> memory_iexplore.log

:start

SET /A count+=1

echo %date% %time% (%count%) >> memory_iexplore.log

%mem% >> memory_iexplore.log

echo --------------------------------------------------------------------------- >> memory_iexplore.log

TIMEOUT 30

goto start


배치 파일명: memory_iexplore.cmd
저장하는 로그 파일명: memory_chrome.log
파일에 저장되는 내용:

--------------------------------------------------------------------------- 
2013-12-24 19:08:11.08 (1) 

iexplore.exe                  1836 Console                    1     27,908 K
iexplore.exe                  6764 Console                    1    175,692 K
--------------------------------------------------------------------------- 
2013-12-24 19:08:41.15 (2) 

iexplore.exe                  1836 Console                    1     27,908 K
iexplore.exe                  6764 Console                    1    178,324 K
--------------------------------------------------------------------------- 

실행되는 모습:


IE Process의 메모리가 증가하는지 쉽게 확인하는 법


Process Explorer 에서 iexplore.exe 프로세스를 찾아서 마우스로 오른쪽 클릭하고, Properties... 메뉴 선택.

Performance Graph 탭을 보면 IE Process만 사용하는 CPU, Private Bytes, I/O 를 볼 수 있다.



Memory Leak 확인 예제 #1

테스트한 브라우저: IE9

테스트 결과: 어느 정도 급격하게 증가한 이후에는 증가하지 않는다.

29.6 MB => 220.2 MB => 411.8 MB => 411.8 MB => 411.9 MB


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <script type="text/javascript">
  5.     function LeakMemory(){
  6.         var parentDiv = document.createElement("div");
  7.         parentDiv.onclick=function() {
  8.           foo();
  9.         };

  10.         parentDiv.bigString =
  11.           new Array(1000).join(new Array(20000).join("XXXXX"));

  12.         alert('Leak Memory Done');
  13.     }

  14. </script>
  15. </head>
  16. <body>
  17. <input type="button" value="Memory Leaking Insert" onclick="LeakMemory()" />
  18. </body>
  19. </html>



Memory Leak 확인 예제 #2

테스트한 브라우저: IE9

테스트 결과: 어느 정도 메모리가 증가한 이후로는 더 이상 증가하지 않았다. (메모리가 많이 증가했다가 alert 버튼을 누르고 나면 다시 원상태로 돌아옴.)

28.1 MB => 39.0 MB => 36.4 MB => 38.4 MB => 37.6 MB => 37.5 MB


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <script type="text/javascript">
  5.     function leak() {
  6.       var e = document.createElement('div');
  7.       var x = { elementReference: e };
  8.       e.jsReference = x;
  9.     }

  10.     function test() {
  11.       for (var i = 0; i < 1000000; i++)
  12.         leak();
  13.       alert('Done');
  14.     }
  15. </script>
  16. </head>
  17. <body>
  18. <input type="button" value="Circular Reference" onclick="test()" />
  19. </body>
  20. </html>


Posted by 1010
00.Flex,Flash,ActionScript2014. 8. 20. 13:49
반응형

[FLEX] Memory leak 문제

Flex Memory Leak 해결 방법

1. addEventListener로 등록된 모든 이벤트들을 removeEventListener로 삭제한다.
- target.addEventListener(eventName,returnFuntion);
- target.removeEventListener(eventName,returnFuntion);

2. 객체타입 멤버 속성들을 null로 셋팅한다.
- 로컬 변수들은 자동으로 소멸되기 때문에 별다른 신경을 쓸 필요가 없지만 멤버 변수로 정의된 객체 타입의 변수들은
인스턴스가 소멸될때 반드시 null로 처리 해야한다.

public class BarGaugeGrid extends ContentBox
{
//멤버 변수
private var _barCount:int = 5;
private var _dataProvider:ArrayCollection;
private var textField:Label;
private var valueLabelField:Label;
private var itemValues:Array = [];
private var dataChanged:Boolean = false;
public function BarGaugeGrid()
{
super();
layout = "vertical";
setStyle("verticalGap", 0);
}

...
public function destroy():void
{
_dataProvider = null;
textField = null;
valueLabelField = null;
itemValues = null;
}



위 2가지만 지켜진다면 대부분의 Memory Leak문제들은 해결된다.


출처 : http://m.truesolution.co.kr/view.php?UID=2613

Posted by 1010
반응형

데이터 유형 설명

프리미티브 데이터 유형에는 Boolean, int, Null, Number, String, uint 및 void 등이 포함됩니다. 또한 ActionScript 기본 클래스에서 Object, Array, Date, Error, Function, RegExp, XML 및 XMLList 등의 복합 데이터 유형을 정의합니다.

Boolean 데이터 유형

Boolean 데이터 유형은 true와 false 두 가지 값으로 구성됩니다. 그 외 다른 값은 Boolean 유형의 변수에 사용할 수 없습니다. 선언되었지만 초기화되지 않은 부울 변수의 기본값은 false입니다.

int 데이터 유형

int 데이터 유형은 내부적으로 32비트의 정수로 저장되며

-2,147,483,648 (-231)부터 2,147,483,647(231 - 1)까지의 정수 집합(-2,147,483,648 및 2,147,483,647 포함)으로 구성됩니다. 이전 버전의 ActionScript에서는 정수와 부동 소수점 숫자에 모두 사용되는 Number 데이터 유형만 제공했습니다. ActionScript 3.0에서는 이제 32비트의 부호가 있거나 없는 정수에 대한 저수준의 시스템 유형에 액세스할 수 있습니다. 변수에서 부동 소수점 숫자를 사용하지 않는 경우 Number 데이터 유형 대신 int 데이터 유형을 사용하는 것이 보다 빠르고 효율적입니다.

최소 및 최대 int 값의 범위를 벗어나는 정수 값인 경우 Number 데이터 유형을 사용하여 양수와 음수 각각 9,007,199,254,740,992(53비트 정수 값) 사이의 값을 처리할 수 있습니다. int 데이터 유형의 변수에 대한 기본값은 0입니다.

Null 데이터 유형

Null 데이터 유형에는 null 값만 포함됩니다. 이는 Object 클래스를 포함하여 복합 데이터 유형을 정의하는 모든 클래스와 String 데이터 유형의 기본값입니다. Boolean, Number, int 및 uint 같은 기타 프리미티브 데이터 유형에는 null 값이 포함되어 있지 않습니다. Boolean, Number, int 또는 uint 유형의 변수에 null을 지정하려고 하면 Flash Player 및 Adobe AIR에서 null 값을 적절한 기본값으로 변환합니다. 이 데이터 유형은 유형 약어로 사용할 수 없습니다.

Number 데이터 유형

ActionScript 3.0에서 Number 데이터 유형은 정수, 부호 없는 정수, 부동 소수점 숫자 등을 나타낼 수 있습니다. 그러나 성능을 최대화하려면 32비트int 및 uint 유형이 저장할 수 있는 값보다 큰 정수 값 또는 부동 소수점 숫자에만 Number 데이터 유형을 사용해야 합니다. 부동 소수점 숫자를 저장하려면 숫자 안에 소수점을 넣습니다. 소수점을 생략하면 숫자가 정수로 저장됩니다.

Number 데이터 유형에서는 이진 부동 소수점 산술에 대한 IEEE 표준(IEEE-754)에 지정된 64비트 배정밀도 형식을 사용합니다. 이 표준에는 64비트를 사용하여 부동 소수점 숫자를 저장하는 방법이 규정되어 있습니다. 1비트는 숫자가 양수 또는 음수인지 지정하는 데 사용됩니다. 11비트는 2를 밑수로 하는 지수를 저장하는 데 사용됩니다. 남은 52비트는 지수에 따라 거듭제곱되는 숫자인 유효 숫자(또는 가수)를 저장하는 데 사용됩니다.

Number 데이터 유형에서는 지수를 저장하는 데 일부 비트를 사용하므로 모든 비트가 유효 숫자를 저장하는 데 사용되는 경우에 비해 매우 큰 부동 소수점 숫자를 저장할 수 있습니다. 예를 들어, Number 데이터 유형에서 유효 숫자를 저장하는 데 64비트를 모두 사용하는 경우, 저장할 수 있는 가장 큰 숫자는 265 - 1입니다. 11비트를 사용하여 지수를 저장하면 Number 데이터 유형에서 유효 숫자를 21023으로 거듭제곱할 수 있습니다.

Number 유형에서 나타낼 수 있는 최소 및 최대 값은 Number.MAX_VALUE 및 Number.MIN_VALUE라는 Number 클래스의 정적 속성에 저장됩니다.

Number.MAX_VALUE == 1.79769313486231e+308 
Number.MIN_VALUE == 4.940656458412467e-324

Number 데이터 유형의 경우 숫자의 범위는 방대하지만 정밀도는 떨어집니다. Number 데이터 유형에서는 유효 숫자를 저장하는 데 52비트를 사용하므로 정확하게 나타내기 위해 52비트 이상이 필요한 숫자(예: 분수 1/3)는 근삿값만 표현할 수 있습니다. 응용 프로그램에서 절대 정밀도의 십진수가 필요한 경우 이진 부동 소수점 산술과 대립되는 십진 부동 소수점 산술을 구현하는 소프트웨어를 사용해야 합니다.

Number 데이터 유형을 사용하여 정수 값을 저장하면 유효 숫자의 52비트만 사용됩니다. Number 데이터 유형에서는 해당 52비트 및 특수 은폐 비트를 사용하여 -9,007,199,254,740,992(-253)부터 9,007,199,254,740,992(253)까지의 정수를 나타냅니다.

Flash Player 및 Adobe AIR에서는 NaN 값을 Number 유형의 변수에 대한 기본값으로 사용할 뿐만 아니라 숫자를 반환해야 하지만 이를 반환하지 않는 모든 연산의 결과로도 사용합니다. 예를 들어, 음수의 제곱근을 계산하려고 하면 결과는 NaN이 됩니다. 기타 특수 Number 값에 양의 무한대와 음의 무한대가 포함됩니다.

참고: 제수가 0인 경우 0으로 나눈 결과도 NaN입니다. 피제수가 양수인 경우 0으로 나누면 무한대가 되고, 피제수가 음수인 경우 0으로 나누면 음의 무한대가 됩니다.

String 데이터 유형

String 데이터 유형은 16비트 문자열을 나타냅니다. 문자열은 내부적으로 UTF-16 포맷을 사용하여 유니코드 문자로 저장합니다. 문자열은 Java 프로그래밍 언어와 마찬가지로 변경할 수 없는 값입니다. String 값에 대한 작업은 해당 문자열의 새 인스턴스를 반환합니다. String 데이터 유형으로 선언되는 변수의 기본값은 null입니다. null 값과 빈 문자열("") 모두 문자가 없다는 것을 나타내지만 서로 다릅니다.

uint 데이터 유형

uint 데이터 유형은 내부적으로 32비트의 부호 없는 정수로 저장되며 0부터 4,294,967,295(232 - 1)까지의 정수 집합(0 및 4,294,967,295 포함)으로 구성됩니다. 음이 아닌 정수를 호출하는 특수한 경우에는 uint 데이터 유형을 사용합니다. 예를 들어 int 데이터 유형에는 색상 값 처리에 적합하지 않은 내부 부호 비트가 포함되어 있기 때문에 픽셀의 색상 값을 나타내려면 uint 데이터 유형을 사용해야 합니다. 최대 uint 값보다 큰 정수 값의 경우에는 53비트 정수 값을 처리할 수 있는 Number 데이터 유형을 사용합니다. uint 데이터 유형의 변수에 대한 기본값은 0입니다.

Void 데이터 유형

void 데이터 유형에는 undefined 값만 포함됩니다. 이전 버전의 ActionScript에서 Object 클래스의 인스턴스에 대한 기본값으로 undefined를 사용했습니다. ActionScript 3.0에서 Object 인스턴스에 대한 기본값은 null입니다. Object 클래스의 인스턴스에 undefined 값을 지정하려고 하면 Flash Player 또는 Adobe AIR에서 이 값을 null로 변환합니다. undefined 값은 유형이 지정되지 않은 변수에만 지정할 수 있습니다. 유형이 지정되지 않은 변수는 유형 약어가 없거나 유형 약어에 별표(*) 기호를 사용하는 변수입니다. void는 반환 유형 약어로만 사용할 수 있습니다.

Object 데이터 유형

Object 데이터 유형은 Object 클래스에 의해 정의됩니다. Object 클래스는 ActionScript에서 모든 클래스 정의에 대한 기본 클래스 역할을 합니다. ActionScript 3.0 버전의 Object 데이터 유형은 세 가지 면에서 이전 버전의 Object 데이터 유형과 다릅니다. 첫 번째, Object 데이터 유형은 더 이상 유형 약어가 없는 변수에 지정되는 기본 데이터 유형이 아닙니다. 두 번째, Object 데이터 유형에는 Object 인스턴스의 기본값이었던 undefined 값이 더 이상 포함되지 않습니다. 세 번째, ActionScript 3.0에서는 Object 클래스의 인스턴스에 대한 기본값이 null입니다.

이전 버전의 ActionScript에서 유형 약어가 없는 변수는 자동으로 Object 데이터 유형으로 지정되었습니다. ActionScript 3.0에는 유형이 지정되지 않은 변수라는 개념이 도입되면서 이전 버전과는 다르게 처리됩니다. 이제 유형 약어가 없는 변수는 유형이 지정되지 않은 변수로 간주됩니다. 변수의 유형을 지정하지 않으려는 자신의 의도를 코드를 읽는 사람에게 분명히 밝히려면 유형 약어로 새 별표(*) 기호를 사용할 수 있습니다. 이는 유형 약어를 생략하는 것과 같습니다. 다음 예제에서는 유형이 지정되지 않은 변수 x를 선언하는 동일한 두 명령문을 보여 줍니다.

var x 
var x:*

유형이 지정되지 않은 변수에만 undefined 값을 저장할 수 있습니다. 데이터 유형이 있는 변수에 undefined 값을 지정하려고 하면 Flash Player 또는 Adobe AIR에서 undefined 값을 해당 데이터 유형의 기본값으로 변환합니다. Object 데이터 유형의 인스턴스인 경우 기본값은 null이며, 이는 Object 인스턴스에 undefined를 지정하려고 하면 Flash Player 또는 Adobe AIR에서 undefined 값을 null로 변환한다는 것을 의미합니다.


Posted by 1010
00.Flex,Flash,ActionScript2014. 7. 30. 15:18
반응형

Apache FlexJS es un nuevo framework creado dentro del proyecto Apache Flex. El Objetivo detrás de este esfuerzo es permitir a los desarrolladores crear aplicaciones escritas en MXML y ActionScript y desplegarlas en todo tipo de entornos, tanto con Flash Player, como sin este, con HTML/JS/CSS.

FlexJS puede generar aplicaciones HTML y funcionar en cualquier navegador sin Flash Player. También puede obtener aplicaciones SWF

FlexJS está basado en el concepto de “frameworks paralelos”, donde los bloques y piezas de construcción existirán tanto en AS com en JS. El compilador (basado en Falcon, la evolución de última generación del compilador de AS3 y MXML), es el encargado de traducir MXML y AS a JS y obtener salidas tanto en SWF como en JS. Es en este último caso donde eliminamos la necesidad del Flash Player pudiendo correr la aplicación en cualquier tipo de navegador.

El siguiente diagrama describe la arquitectura sobre la cual se basa el diseño de FlexJS:

Apache FlexJS Diagrama de Cajas

Apache FlexJS Diagrama de Cajas

Compatibilidad con el SDK actual

El SDK actual tiene mucho código fruto de su evolución a lo largo de los años (tengamos en cuenta que el framework tiene ya alrededor de 10 años). Por tanto la dirección que se ha tomado es hacer una base de código desde cero. Esto en la práctica significa que las aplicaciones actuales creadas con versiones del SDK actual no podrán ser compiladas con FlexJS. No obstante, la reescritura de estas aplicaciones tenderá a ser más sencilla en FlexJS dada la similitud que con otra tecnología.

De hecho, el nuevo framework tiene que funcionar bajo un subconjunto de funcionalidades ya que la tecnología de los navegadores y Javascript todavía no es equiparable a las capacidades de Flash Player. Es por tanto evidente, que para conseguir aplicaciones HTML/JS/CSS estás tienen que estar pensadas bajo las restricciones de este entorno.

Ventajas

A parte del acceso a dispositivos donde Flash Player nunca estará presente (principalmente los navegadores iOS), las ventajas para el desarrollado de aplicaciones son claras. Usar el conjunto de herramientas y lenguajes altamente tipados y con orientación a objetos que son fundamentales para todas aquellas aplicaciones de tamaño y funcionalidad medio alto. Se trata pues de recuperar muchas de las características perdidas cuando pasamos del mundo Flash/Flex a un mundo puramente Javascript.

Demo

El ejemplo inicial creado por Alex Harui pone de manifiesto la potencia que puede tener este nuevo framework si observamos las versiones flash y html. La captura siguiente ofrece una visión de cada una de las versiones compiladas con FlexJS (SWF y JS):

Flex JS Demo Inicial

Flex JS Demo Inicial

En el ejemplo se recuperan valores de bolsa de una fuente de datos remota y nos permite pedír el precio. La versión SWF se encuentra aquí y la versión HTML/JS aquí. El código fuente se puede ver seleccionando la opción View Source del menú conextual en la versión Flash.

Estado

FlexJS está en desarrollo por un grupo pequeño pero constante de desarrolladores de Apache Flex encabezado por Alex Harui (desarrollador del SDK desde los comienzos de Flex en Macromedia, actual PMC Chair del proyecto y uno de los principales soportes para el resto de usuarios de Flex por sus contribuciones en la lista de flexcoders). Los puntos acordados para adquirir el estado alpha son:

  • Integración del compilador con Flash Builder
  • Soporte para la creación de Aplicación y Vistas
  • Soporte de propiedades CSS personalizadas
  • Implementación inicial del componente DataGrid
  • Implementación inicial del componente Chars

Algunos de estos puntos ya están conseguidos o cerca de ser completados por lo que es de esperar que la fecha de salida de la versión alpha del producto esté cercana.

Desde el proyecto se pide ayuda de la comunidad para avanzar de forma más rápida. El modelo de Apache se basa en voluntariado y por tanto el avance de FlexJS está directamente relacionado con el soporte de toda la comunidad.

Enlaces

Para leer más sobre FlexJS os proporciono los siguientes enlaces de forma que podáis investigar más sobre este prometedor framework:

 Wiki sobre FlexJS en Apache Flex
 Charla sobre FlexJS ofrecida por Alex Harui por Adobe Connect
 Using FlexJS with Adobe Flash Builder
 Apache Flex Jira
 Lista de correo en Apache Flex (mucho tráfico, para leer sobre FlexJS filtrar por el prefijo “[FlexJS]“)

Conclusión

Hablaremos mucho de FlexJS en Made In Flex, ya que ofrece no solo un futuro prometedor para los desarrolladores Flex que no quieren perder la rapidez y solvencia de sus entornos y lenguajes, para volver a las incomodidades de la programación JS, si no que además aporta verdadera innovación en la conceptualización del propio framework.

Lo veremos con la capacidad de FlexJS para añadir distintos juegos de componentes (html, jquery, createjs,…), la reducción extrema del peso y tamaño de la aplicación generada -tanto en SWF como en JS- o los nuevos conceptos de Strands y Beads, que permiten promocionar la composición sobre la herencia, eliminando la existencia de god classes(como UIComponent con 15.000 lineas de código en el SDK actual).

De forma rápida, en FlexJS un Bead es una funcionalidad que se puede añadir o quitar de un componente como un verdadero plugin, de forma que podemos usarla cuando realmente los necesitemos en la más pura filosofía “pay-as-you-go” (ej: Añadir funcionalidades de touch en dispositivos móviles, mientras que en navegadores y escritorio tenemos funcionalidades de roll over).

Volvemos en breve para ver más temas sobre Apache FlexJS.

¡Comparte este artículo si te ha parecido interesante!

Posted by 1010
02.Oracle2014. 7. 29. 17:57
반응형

Oracle JDBC의 CachedRowSet Implement Bug

CachedRowSet을 사용하는 경우 Oracle JDBC의 CachedRowSet Implementation Bug 때문에 더 이상 고생하는 개발자가 없기를 바라며 이 글을 작성한다.

결론부터 말하자면 Oracle의 JDBC Driver가 CachedRowSet을 Implement한 Class의 Method 중 Date Type에 대한 Bug로 인해 CachedRowSet을 이용한 Code에서 오류가 발생될 수 있다는 것이다. 그리고 이는 Patch된 최신 Version인(현재 시점) 'ojdbc5.jar' 이상을 사용해야 해결될 수 있는 문제라는 것이다.

아래의 몇 몇 오류들은 Bug가 있는 - 오류가 Patch되지 않은 - Oracle JDBC를 이용해 CachedRowSet을 사용할 경우 발생될 수 있는 Error Message들의 예로, 궁극적인 문제의 원인은 Date Type의 DB Field를 CachedRowSet의 'getDate()' Method로 참조할 때 발생하는 'java.lang.ClassCastException'이 바로 그 것이다.
 
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not roll back JDBC transaction; nested exception is java.sql.SQLException: Connection is closed.
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:583)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
javax.servlet.http.HttpServlet.service(HttpServlet.java:627)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

[ErrorMessage-1]
  • ErrorMessage-1
       Spring Framework를 연동했을 때 발생될 수 있는 예로 'ClassCastException' 발생으로 인해 해당 Transaction에 대한 Roll back을 Spring Framework이 AOP를 통해 수행하려는 상황에서 발생된 오류인데, 이 오류는 엄밀히 말하면 일반적인 상황에서는 발생하지 않을 수도 있다. 왜냐하면 이 상황은 필자의 경우로 'Javassist'를 이용해 Connection Object를 Intercept한 경우이기 때문이다.

org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [null]; error code [0];
--- The error occurred in sqlMap.xml.
--- The error occurred while applying a result map.
--- Check the scope.select.
--- Check the result mapping for the 'createDate' property.
--- Cause: java.lang.ClassCastException: java.sql.Timestamp; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
--- The error occurred in sqlMap.xml.
--- The error occurred while applying a result map.
--- Check the scope.select.
--- Check the result mapping for the 'createDate' property.  
--- Cause: java.lang.ClassCastException: java.sql.Timestamp
[ErrorMessage-2]

  • ErrorMessage-2
       Spring Framework와(과) iBATIS를 연동했을 경우 debugging 과정에서 추출한 Error Message로 오류의 원인을 정확히 짚어내고 있다. 하지만 오류내용을 보면 마치 'java.sql.Timestamp' Type을 다른 Type으로 잘 못 Casting해 발생한 오류인 것 처럼 보인다.
사실과 다르게 말이다. Error Message에 보이는 'createDate'라는 Field는 분명 'Date' Type인 것이다. 또한 CachedRowSet이 아닌 ResultSet으로는 전혀 문제가 발생하지 않은 Code이다.

이 문제의 원인을 찾는데 상당히 많은 시간을 소요했으며 정신적 스트레스 또한 말 할 수 없이 많았다. 지금까지의 Posting 글이 그러했듯 같은 오류에 대해 적어도 필자 보다는 적은 시간과 노력으로 좋을 결과를 얻기를 바라는 마음이다.
국내 검색 Site에서는 관련 오류 내용을 검색조차 할 수 없었으나 역시 Googling을 통해 알게된 외국의 한 Community Site에 올린 누군가의 글로 해결의 실마리를 찾게 되었다. 물론 이 도 쉽지많은 않앗지만 말이다. 아무튼 도움이 되길 바라며 Posting한다.


출처 : http://mobicator.blogspot.kr/2010/04/oracle-jdbc-cachedrowset-implement-bug.html

Posted by 1010
반응형
mx.utils 에는 편하게 사용할 수 있는 api 가 담겨있다. 

ObjectUtil.toString 같은 것은 많이 사용하고 꽤나 유용하게 쓰인다. 

이 포스트에서 이야기 할 substitute 는 간단하게 이야기 하면 문자열을 치환 해주는 메서드이다.

간단히 코드를 보면 이해가 될것이다. 

1private var str:String "select * from TABLE_NAME where userid='{0}' and menuid='{1}'";
2 
3private function build():void
4{
5    var query:String = StringUtil.substitute(str,"rinn","n002");
6    trace(query);
7    //select * from TABLE_NAME where userid='rinn' and menuid='n002'
8}


위코드에서 보듯이 String 값에서 {n} 형식으로 중괄호로 묶여진 것들을 찾아서. 
거기에 파라미터로 받은 값을 넣어준다. 
n 값에 따라서 파라미터가 순서대로 들어가게 된다. 

{0} 에는 첫번째가 {1}에는 두번째가 들어가고 문자열 안에서 등장하는 순서는 상관이 없다.

1private var str2:String "select * from TABLE_NAME where userid='{0}' and menuid=(select menuno from TABLE2 where userid='{0}' and menuid='{1}')";
2 
3private function build2():void
4{
5    var query:String = StringUtil.substitute(str2,"rinn","n002");
6    trace(query);
7    //select * from TABLE_NAME where userid='rinn' and menuid=(select menuno from TABLE2 where userid='rinn' and menuid='n002')
8}


{0} 이 두번 나오면 두번다 파라미터 첫번째 값이 들어가게 되는 것이다. 

이 방법의 좋은점은 대상이 되는 문자열을 파일로 저장해놓을 수 있다는 점이다. 

기본 쿼리 같은 경우 파일로 저장해놓고 불러다가 치환해서 사용하게 되면.. 테이블 이름정도가 바뀐다거나 하는 것들은 간단하게 파일만 수정하는 것으로도 해결이 된다. 

메뉴의 링크 정보를 저장하는 경우에도 편리하다. 권한 정보 같은것에 따라 파라미터가 바뀐다거나 하는 경우에.. 기본 링크 정보를 저장해놓으면 이후에 간단한 것은 컴파일 할 필요없이 수정해 줄수 있다. 

사실 쿼리 같은 경우 iBatis 를 쓰는 것이 훨씬 간편하고 다이나믹 하게 쿼리를 생성할 수 있지만. 간단한 httpService 를 사용하는 경우에는 유용할 수 있겠다. 

substitute 함수는 간단하게 구현되어있다. 

01public static function substitute(str:String, ... rest):String
02{
03    if (str == nullreturn '';
04     
05    // Replace all of the parameters in the msg string.
06    var len:uint = rest.length;
07    var args:Array;
08    if (len == 1 && rest[0is Array)
09    {
10        args = rest[0as Array;
11        len = args.length;
12    }
13    else
14    {
15        args = rest;
16    }
17     
18    for (var i:int 0; i < len; i++)
19    {
20        str = str.replace(new RegExp("\\{"+i+"\\}""g"), args[i]);
21    }
22 
23    return str;
24}


간단하게 받아온 파라미터를 Array로 받고 for문 돌면서 정규식으로 찾아서 바꿔주는 구조이다.
사실 이런것은 필요한 경우에 그냥 만들어서 쓰게 되는데 이미 api 에서 제공하는 것을 귀찮게 만들어 쓸 필요가 있는가!!
그냥 있는 걸 쓰자.. -ㅅ-;

mx.utils 패키지 같은 경우에 간단 간단한 함수로 되어있기 때문에 api 소스를 보는 것 만으로도 초보 개발자에게는 공부가 된다. 결정적으로 그리 어렵지 않다.. 

시간날때 한번씩 뭐가 있는지 봐놓으면.. 나중에 뻔히 제공되는 메서드를 놔두고.. 개발하는 사태는 벌어지지 않을것이다.. 
우리는.. 그거 말고도 할일이 많지 않은가.. ㅠㅠ


아.. 주말도 출근 해야 하나.....

출처 : http://rinn.kr/31


Posted by 1010