2009년 12월 26일 토요일

안드로이드 2.0.1 HelloWorld!

안드로이드 개발 환경 설정 및 HelloWorld를 띄워보았다.

첨엔 이 망할 것이 메뉴얼대로 했는데도

내가 찍고자 했던 텍스트가 나타나지 않아 날 헤메게 만들었다.

허나, 모든 개발 관련 포스팅의 말미에 있던 "시뮬레이터가 무지 오래 걸린다." 라는 말을 보고는

인내심을 갖고 화면이 넘어가기만을 기다려 보았다.

그랬더니 안드로이드 초기화면으로 사료되는 휴대폰 대기화면이 보였다.

 

처음보는 안드로이드 폰의 기본화면에서

대체 내가 만든 프로그램은 어디있는거야? 하고 투덜대다 아이팟터치를 쓸때의 느낌으로

하고 자물쇠를 오른쪽으로 끌어 보니

짜잔!! 드디어 내가 만든 프로그램(?)을 만나볼 수 있었다.

성공적으로 출력된 모습

 

느낌은

JAVA의 RCP나 SWING, JWT 프로그래밍을 해본 사람이라면

쉽게 접근 할 수 있으며,

.NET 의 GUI 개발환경을 하던 사람이라면

아! 다시 짜증나는 JAVA의 GUI 개발이구나 하고 욕을 뱉을 지 모르겠다.

저 조그만 시뮬레이터안에 기본으로 깔려있는 메시징이나 웹서핑 동작하는 모습이 신기했다.

이제 시작인가? ㅋㅋ 컴터랑 모니터를 바꾸고싶은데

돈이 없다 엉엉 ㅠㅠ

2009년 12월 23일 수요일

스케쥴러에 의한 대용량 처리

 열심히 적었던 글이 소스코드를 옮겨 넣는 와중에 고향 친구로부터 전화가 모르고 저장. 그대로 날라가 버렸다. 슬프다. ㅠㅠ

같은 말을 반복하는걸 매우 싫어하는 성격이므로 기억을 더듬어 대략 다음의 내용들을 적어본다.

 

1. 대용량 처리를 위해서는 작업 정보를 관리하고 정확 모니터링 할 수 있는 스케쥴러가 필요하다.

2. 스케쥴러 또한 하나 이상의 쓰레드로 구성되므로, 쓰레드의 특성에 대해 정확하게 알고 있어야 한다.

3. 쓰레드 사용시 야기되는 문제들 가운데 Race Condition에 따른 해결방법, 뮤텍스, 이벤트 세마포어, 크리티컬 섹션, 등을 이해하고 있어야 한다.

4. 자바의 최신 SDK에는 기존에 사용되던 suspand(), resume() 과 같은 메서드가 디프레취 되었고, 쓰레드를 중단하거나 재시작하는 것과 같은 생명주기를 개발자가 임의로 컨트롤하기 보다는 이전 생명주기를 완전히 종료시키고 다시 해당 쓰레드의 생명주기를 시작하는 방법을 더 권장하는것 같다.

5. 스케쥴러는 작업 도중 예기치 않은 오류를 복구하는 기능과, 실시간으로 서버 시스템의 자원(메모리 등)을 모니터링 할 수 있는 기능을 가져야 한다.

6. log4j와 같은 로그 툴을 통해 로그를 잘 남겨두어야 한다.

7. 작업 정보를 저장하는 데이터 구조인, Queue, Stack, List 등의 특성과 사용법을 잘 이해하고 있어야 한다.

8. 스케쥴러는 시스템에 과도한 부하를 주는것을 방지하고 시스템 성능등과 같은 이슈를 피하고 단위 작업에 대한 무결성을 위해 자신에게 할당된 작업을 분할 처리 할 수 있는 기능을 가져야 한다.

 

[code java] /**
 * <PRE>
 * Class/Interface Name : WebSchedulerINI
 * History
 *     1. 이지홍(hongsgo@gmail.com), 2009. 1. 8., 최초작성
 * </PRE>
 * @brief 색인 서버의 전체 작업을 관리하는 핵심 클래스.
 * @date 2009. 1. 8.
 * @version 1.0.0
 * @author 이지홍(hongsgo@gmail.com)
 * @warning
 */
public class WebSchedulerINI extends BaseService implements Runnable {
 
 //스케쥴러 쓰레드
 private Thread webjobs = null;
 //스케쥴러의 모니터링을  담당하는 클래스
 private MonitorService monitorService = null;
 //실패한 작업을 복구하는 클래스
 private JobRecoveryManager jobRecoveryManager = null;
 //스케쥴러의 작업을 비우는 플래그 클래스(최우선)
 private boolean job_clear=false;
 //서버 시작 간을 표시하기 위한 부분 .
 public Date run_time = null;  /**
  * <PRE>
  * Method Name : setSchedulerJobClearFlag
  * History
  *  1. 이지홍(hongsgo@gmail.com), 2009. 2. 18., 최초작성
  * </PRE>
  * @brief 다음 작업에 스케쥴러를 비우는 플래그
  * @date 2009. 2. 18.
  * @version 1.0.0 void
  */
 public void setSchedulerJobClearFlag(){
  this.job_clear=true;
 }  /**
  * <PRE>
  * Method Name : setSchedulerJobClear
  * History
  *  1. 이지홍(hongsgo@gmail.com), 2009. 2. 18., 최초작성
  * </PRE>
  * @brief  플래그에 따라 큐를 비우는 메소드
  * @date 2009. 2. 18.
  * @version 1.0.0 void
  */
 private void setSchedulerJobClear(){
  this.monitorService.getSchedulerMonitorInfo().setSplit_job_count(0L);
  this.que_webSchedulerJob.clear();
  this.job_clear=false;
 }  /**
  * 임시 작업결과 정보
  */
 private DBIndexJobInfo temp_dbjob = null;
 private OperateJobInfo temp_operator_job = null;
 private MemoryManager memoryManager = null;
 private DBIndexService dbIndexService = null;
 //색인을 처리하는 객체의 부모클래스
 private BaseIndexService baseIndexService = null;
 private String[] temp_arr_heapinfo = null;
 private boolean index_optimize = false;
 private int now_SCHEDULER_INDEX_OPTIMIZE_COUNT = 0;
 
 //스케쥴러 작업의 최상위 부모 클래스
 private BaseWebJobInfo temp_basejob= null;
 private Queue<BaseWebJobInfo> que_webSchedulerJob = null;
//
 private ArrayList<String[]> arr_indexdata= null;  /* (non-Javadoc)
  * @see com.jce.searchengine.common.service.BaseService#init()
  */
 public void init(){
  this.webjobs= new Thread(this);
  this.que_webSchedulerJob=new PriorityBlockingQueue<BaseWebJobInfo>(100,new JobtypeCompare());
  this.run_time=new Date();
  webjobs.start();
 }
 public int getQue_Count(){
  return this.que_webSchedulerJob.size();
 }     /**
     * <PRE>
     * Class/Interface Name : JobtypeCompare
     * History
     *     1. 이지홍(hongsgo@gmail.com), 2009. 2. 6., 최초작성
     * </PRE>
     * @brief 작업의 우선순위를 기준으로 비교하는 Sorter
     * @date 2009. 2. 6.
     * @version 1.0.0
     * @author 이지홍(hongsgo@gmail.com)
     * @warning
     */
    class JobtypeCompare implements Comparator<BaseWebJobInfo> {      public int compare(BaseWebJobInfo o1, BaseWebJobInfo o2) {
     if(o1 instanceof BaseWebJobInfo && o2 instanceof BaseWebJobInfo){
      BaseWebJobInfo s1 = (BaseWebJobInfo)o1;
      BaseWebJobInfo s2 = (BaseWebJobInfo)o2;
              return (s1.getPriority() < s2.getPriority())? 1 : (s1.getPriority() == s2.getPriority() ? 0 : -1);
        }
        return -1;
  }
 }         /** (non-Javadoc)
  * @see java.lang.Runnable#run()
  * 주기적으로 rss,atom,메인 플래쉬 상단 xml을 퍼블리싱 하는 메서드 (1분간격)
  */
 public void run(){
  while(true)
  {
    //꺼내기전에 현재 큐를 비우는 플래그나 특정 사이트의 작업을 지우라는 플래그가 있는지 확인한다.
    //새 작업을 꺼낸다.
    if(job_clear){
     this.setSchedulerJobClear();
    }
    else
    {      this.temp_basejob=this.getNextJob();
     if(this.temp_basejob!=null)
     {
      switch(this.temp_basejob.getWebSchedulerJobType()){
       case DBIndex: //디비인덱스 작업일 경우 수행
        try
        {
        
          //보다 명확한 작업객체의 유효성 검사.
          if(this.temp_basejob instanceof DBIndexJobInfo){
           temp_dbjob = (DBIndexJobInfo)this.temp_basejob;
           if(temp_dbjob != null)
           {
            //작업 처리 여부 플래그를 활성화 시켜두고
            //디비 인덱스 작업의 타입에 따라 디비기반 인덱싱 작업 수행
            //해당 색인경로에 락이 남아있다면 락을 제거하여 준다.
            //데이터베이스로 부터 데이터를 가져오기 전에 log4j를 통해 info레벨의 log를 남긴다.
            //메모리 매니저를 통해 현재 가용중인 메모리의 정보를 갱신한다.            }
           else{
            throw new NullPointerException(super.glbConstant.getSCHEDULER_ERROR_6());
           }
          }
          //instanceof가 null인지 아닌지 확인하는 로직.
          else{
            //작업정보에는 데이터베이스 작업이라고 했지만, 실제 형변환을 해보니 아니더라. 그래서 예외 던짐.
            throw new ClassCastException(super.glbConstant.getSCHEDULER_ERROR_1());
          }
        } catch (Exception e) {
         this.monitorService.getSchedulerMonitorInfo().setIncFailJobCount(); //실패작업 카운팅
         if(this.temp_dbjob.getJobtype().equals(DBIndexJobType.DBIndexMakeAll)){
          //현재 처리중인 작업이 일괄색인이었다면 하위 분할된 작업값들을 초기화
          this.monitorService.getSchedulerMonitorInfo().setSplit_job_count(0);
         }
         if(e instanceof SQLException){
          if(!(this.temp_dbjob.getJobtype().equals(DBIndexJobType.DBIndexMakeAll))){
           this.jobRecoveryManager.setReadyToFailedJobSave(temp_dbjob);
          }
          logger.error(super.glbConstant.getSCHEDULER_ERROR_5()+ e.getMessage());
         }
         else if(e instanceof IOException){
          if(e instanceof CorruptIndexException){
           logger.error(super.glbConstant.getSCHEDULER_ERROR_3()+ e.getMessage());
          }
          else if(e instanceof LockObtainFailedException){
           try {
            this.dbIndexService.setDelLock(temp_dbjob.getService_site_info().getArr_list_index_location().get(0));
           } catch (IOException e1) {
            logger.error(e1.getMessage());
           }
           logger.error(super.glbConstant.getSCHEDULER_ERROR_1()+ e.getMessage());
          }
          else{
           logger.error(super.glbConstant.getSCHEDULER_ERROR_2()+ e.getMessage());
          }           if(!(temp_dbjob.getJobtype().equals(DBIndexJobType.DBIndexMakeAll))){
           this.jobRecoveryManager.setReadyToFailedJobSave(temp_dbjob);
          }
         }
         else if(e instanceof NullPointerException){
          logger.error(super.glbConstant.getSCHEDULER_ERROR_4()+e.getMessage());
         }
         else if(e instanceof ClassCastException){
          logger.error(super.glbConstant.getSCHEDULER_ERROR_1()+ e.getMessage());
         }
         else{
          logger.error(super.glbConstant.getSCHEDULER_ERROR_5()+ e.getMessage());
         }
        } finally{
         //플래그를 바꾼다.
         temp_dbjob.getService_site_info().setJobflag(false);
         this.setIndex_optimize(false);
         this.monitorService.getSchedulerMonitorInfo().setIncMonitorCount(temp_dbjob.getJobtype());
        }
        break;
       case Operate:
        //운영 툴 작업과 관련된게 있으면 처리
         try {
           if(this.temp_basejob instanceof OperateJobInfo){
            this.temp_operator_job = (OperateJobInfo)this.temp_basejob;
           }
           if(this.temp_operator_job != null)
           {
            switch(this.temp_operator_job.getJobtype()){
             case Clear:
               //로케이션이 하나라고 생각하고 처리.
               this.baseIndexService.setClearIndex(super.getIndexInfoLoaderService().getIndexInfoXmlSaxHandler().getHash_table_content_service_site().get(this.temp_operator_job.getSitecode()).getArr_list_index_location().get(0));
             break;
             case Optimize:
               this.baseIndexService.setOptimizeIndex(super.getIndexInfoLoaderService().getIndexInfoXmlSaxHandler().getHash_table_content_service_site().get(this.temp_operator_job.getSitecode()).getArr_list_index_location().get(0));
             break;
            }
           }
           else{
            //작업정보에는 데이터베이스 작업이라고 했지만, 실제 형변환을 해보니 아니더라. 그래서 예외 던짐.
            throw new ClassCastException(super.glbConstant.getSCHEDULER_ERROR_1());
           }
         } catch (Exception e) {
          logger.error(e.getMessage());
         } finally{
          this.monitorService.getSchedulerMonitorInfo().setIncMonitorCount(this.temp_operator_job.getJobtype());
         }
        break;
       case FieldInex:
        //쿼리 인덱스 혹은 필드 인덱스 작업이 있으면 수행.
        break;
       default:
        break;
      }//switch 문 끝       //현재 끄집어 낸 작업을 지우고
      this.que_webSchedulerJob.remove();
      //작업 처리 횟수를 올린다.
      this.monitorService.getSchedulerMonitorInfo().setIncTotJobCount();
      if(this.arr_indexdata!=null){
       this.arr_indexdata.clear();
      }
      this.arr_indexdata=null;
      this.temp_arr_heapinfo = null;
      this.temp_dbjob = null;
      this.temp_basejob=null;
     }
     else{// 작업이 없다면 다음 실행.
      try {
       //idle 횟수를 기억해 두었다가 최적화를 수행함
       if(now_SCHEDULER_INDEX_OPTIMIZE_COUNT>Integer.parseInt(super.getGlbConstant().getSCHEDULER_INDEX_OPTIMIZE_COUNT()) && this.isIndex_optimize()==false){
        this.now_SCHEDULER_INDEX_OPTIMIZE_COUNT=0;
        this.index_optimize=true;
       }
       else{
        now_SCHEDULER_INDEX_OPTIMIZE_COUNT++;
       }
      } catch (Exception e) {
       logger.error(this.glbConstant.getSCHEDULER_ERROR_8()+e.getMessage());
      } finally{       }      }
     this.temp_basejob = null;
     //if-else 문 끝
     try {
      if(this.que_webSchedulerJob.isEmpty()){ //작업 큐가 비었다면 idle
       Thread.sleep(this.glbConstant.getSCHEDULER_RELOAD_TIME());
      }
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }   } //while문 끝
 }  public BaseWebJobInfo getNextJob(){   return this.que_webSchedulerJob.peek();
 }   [/code]

2009년 12월 20일 일요일

AVATAR 감상 후기

어제 강남에서 제임스카메론의 아바타(AVATAR)를 보고 왔다.

비록 디지털 3D 좌석은 아니었지만 긴 러닝타임 동안 집중 하고 볼 수 있었다.

가끔 여자친구가 앵겨서 마치 극 속에서 아바타 조종사의 링크가 강제로 끈기는 것 같은 경험을

몇번씩 겪었지만.. 그것또한 행복했다.

 

아바타의 러닝타임은 충분했지만 영화가 던지는 메시지를 모두 다 이해하기엔 그렇게 긴 시간이 아니었다. 오히려 더 많은 것들을 준비했지만 그것들을 여유롭게 둘러 볼 시간은 부족한 느낌을 받았다.

아름다운 세계관과 메카닉, 그리고 판타지, 이러한 재료들을 잘 조합하여 작품성과  흥행성을 모두 다 잡으려는 제임스 카메론 감독의 마음이 엿보였다.

그에게 또 한 번 감명 받았던 점은 이렇게 훌륭한 영화를 만들기 위해 각 분야의 최고의 사람들 혹은 최고가 되려고 한 사람들과 함께 작업 했을텐데, 어떻기 수년의 시간동안 그들을 이끌어 실패하지 않고 그들로부터 최고의 결과물을 뽑아 낼 수 있었을까 하는점이다.

정말 위대한 감독은 하늘이 정해주는 것 같다.

스태프들을 잘 관리한 그의 용병술은 열연을 펼친 배우들에게서도 찾아 볼 수있다. 흥행배우가 아닌, 터미네이터, 에어리언등을 통해 이미 검증된, 또 그와 함께 작업했던 배우들을 등장 시킴으로써 흥행배우의 이름으로 떠벌리는 영화가 아니어서 더 좋았던거 같다.

여자친구가 주인공 여자 외계종족이 흑인과 닮았다고 했었는데, 난 어딜봐서 흑인같은지 잘 몰랐다.

헌제 집에 돌아와 구글링을 해보니 여자 배우 및 외계 종족의 여자추장을 포함해 흑인 배우가 연기했음을 알았다. 여자추장 배우는 더 쉴드에서 자주 보였던 배우였고, 여자 주인공 배우는 잘 모르겠다.

 

재밌었던 포인트는 여자 주인공의 아버지가 죽어도 눈물이 흐르는 장면은 없었는데,

남자 주인공에 산소마스크를 씌워주고 슬퍼할때는 곧바로 눈물을 흘리는 점과

남자주인공이 배신을 해서 마을이 다 부서지자 여자주인공이 남자주인공에게 다시는 오지 말라고 했는데, 남자주인공이 막토가 되어 용한마리 타고 오니까 여자 주인공이 한 대사.." 난 내 가족을 지키고 싶었어"  이건 뭥미?

역시 남자는 권력이고 여자는 거기에 반한다는건 전 우주를 통틀어 진리라는 것인가? 슬프다. ㅋㅋ

 

로이모와 줄리엣처럼 달콤했던 "I see you"  난 당신을 봅니다. 라는 대사가 너무 좋았고.

두번 세번 가슴이 울컥 할 정도의 떨림을 느끼게 해준 스트레스가 확 풀리는 영화였다.

 

 

 

 

 

2009년 12월 12일 토요일

헤바온라인 OBT 참가

아 겟 엠프드 만든 회사에서 만든 RPG 인데

첨엔 애들 게임이려니 했는데

몰입감이 엄청나다.

그.. 몬스터를 수집하고 키워나가면서 주 캐릭터도 전직을 하고..

머 대략 그런

 

 

난 늘 그렇듯 마법사를 선택하고

 

회피율 버프 해주는 소환수를 데리고 다닌다능..

 

2009년 12월 10일 목요일

색인과 검색 그리고 하이라이팅

무엇을 검색 해야 하는가에 대한 고민은

무엇을 색인해야 하는가로 이어진다.

색인 할 그 무언가가 정해진다면,

다시 어떻게 그 검색 결과를 하이라이팅 해서 보여 줄 것인가로 귀결된다.

참 아이러니 한 이야기다.

 

게시판의 글들을 색인하는 것을 예로 들어보자.

우리는 수 많은 게시글들을 검색 대상으로 하였기에,

다양한 성향의 유저들이 쓴 글을 색인엔진에 밀어 넣어야만 했다.

 

서로 다른 옵션과 코드들을 가진 에디터가 뱉어내는

게시글들로부터 양질의 색인 데이터를 뽑아내는것이 목표였다.

또한 검색된 결과에 게시글들이 가지고 있던 혹은 악의적으로 입력된 내용이

결과 페이지의 레이아웃을 망치거나 사이트의 보안 요소를 헤쳐서는 안되었다.

에디터가 escapeHTML을 수행하는지 아닌지, 에디터에 html 태그를 허용하거나 embed,object를 허용 했는지의 여부에 따라 색인시 준비해야 할 전처리기의 명세는 달라지게 되지만,

너무 많은 전처리 작업을 수행하면 유지보수 비용이 높게 발생하거나,

한정된 시스템에 부담을 주거나 또는 신규 작성된 컨텐츠가 색인에 반영되기까지의

시간 차가 커지는 원인이 되었다.

마지막으로 양질의 검색 결과에 HTML태그와 CSS를 이용한 하이라이팅 기능이

가능해야 했으므로, HTML 태그를 무조건 제거 할 수는 없는 상황이었다.

 

무작정 달려들었다간 수만건의 데이터를 쓸모없게 만들거나,

테스트 시간만도 상당한 작업을 계속 반복하는 시행착오를 겪을 판이었다.

 

차 한잔 마실 시간이 흘렀을까?

APACHE COMMONS LANG 패키지에 포함된 StringEscapeUtils 객체가 퍼뜩 떠올랐다.

내가 색인해야 할 원본 데이터가 결과를 출력 할 HTML 페이지의 내용을 가짐으로 해서

문제가 발생한다면, 헌데, 그 내용이 HTML ESCAPE 유무의 차가 있다면,

전처리기에서 색인 할 데이터를 HTML ESCAPE 처리하여 StringUtils에

HTML REMOVE REGEXPRESSION을 사용하여 관계되는 태그를 모두 다(거의 다) 제거하면 어떨까 하는 생각이었다.

그 다음은, 그렇게 걸러진 게시글들안에 어쩔 수 없이 남아있는 새로운 태그들에 대한 처리( <!-- --> 나 < >, OBJECT, Embed 와 같은 것들 이 문제가 되었지만)를 하고 색인 파일을 만들어낸다.

그 뒤,  검색서버가 색인파일로부터 검색한 결과를 추출해내면, 하이라이팅 작업을 더해 결과를 만들어 내도록 구현하면 되었다.

[code java] //데이터베이스로부터 가져온 게시물의 정보를 담은 ArrayList<String[]> 객체 arr_indexdata           int counter=0;
     for(String[] arr_string: arr_indexdata){
      doc[counter]=new Document();
       //필드 정보를 바탕으로 문서를 작성한다.
      for(int j = 0 ; j < arr_fieldinfo.size();j++){
       //html escape로 일괄 변환후 html 태그를 제거한 내용을 저장
       doc[counter].add(new Field(arr_fieldinfo.get(j).getName()
          ,StringEscapeUtils.escapeHtml(arr_string[j]).replaceAll("(?:<!--.*?(?:--.*?--\\s*)*.*?-->)|(?:<(?:[^>'\"]*|\".*?\"|'.*?')+>)","")
          ,arr_fieldinfo.get(j).getStoretype()
          ,arr_fieldinfo.get(j).getIndextype()));
      }
      counter++;
     }
     arr_indexdata.clear();
     arr_indexdata = null; [/code]

 

 

검색엔진 개발정리를 시작하다. lucene 2.9

Apache Lucene - Overview

 

오픈소스 검색엔진 루씬 2.9 버젼으로 개발을 진행하고 있다.

곧 오픈 할 예정,

이미 루씬을 사용해서 7개월 이상 개발해 왔으나,

이제서야 그것에 대한 이야기들을 이 공간에 남겨보고자 한다.
당장은 루씬을 활용법이나 루씬을 실 업무에 적용했을때 발생하는 문제들이

주류를 이룰 것이고(내가 관심이 있는게 그쪽이니), 그 나머지는

루씬의 핵심 알고리즘들을 파헤치고 싶다.(욕심)

 

여력이 남는다면 내가 만든 결과물을 정리해서 별도로 검색엔진 운용도 해보고싶다.

블로그에 소스 코드 보이기 (Syntax Highlighter)

<Code Highlighter 사용 예>

 

코드 냄새가 나는 블로그를 읽다보면

위와 같이 코드가 이쁘게 나오는 걸 볼 수 있다.

내 블로그에도 적용해보고싶어, 어떻게 하면 할 수 있을까? 하고 찾아보니

텍스트큐브 도움말 페이지에서 Syntax Highlighter를 찾을 수 있었다.

오오 이거 좋은데?

내가 친 소스 코드가 이렇게 이뿌게 바뀌다니..

 

[code java]

super.Request_Status=super.setValidateParams(this.temp_dbindexjobinfo.getSitecode()
      ,this.temp_dbindexjobinfo.getContentidx()
      ,this.temp_dbindexjobinfo.getPriority()
      ,this.temp_dbindexjobinfo.getJobtype().name()
      ,this.temp_dbindexjobinfo.getService_site_info()
      ,this.temp_dbindexjobinfo.getWebSchedulerJobType()
    );

[/code]

2009년 12월 9일 수요일

SunOS에 JVMPI를 통해 REMOTE DEBUGGING 하기

회사의 개발서버를 모니터링 할 수 있는 방법이 없을까 고민하다

JVMPI를 알게되었다.

소스는 로컬에 있는걸로 개발서버 및 테스트 서버에 접속하여

이벤트를 기준으로 JVM의 상황을 모니터링 할 수 있다.

이벤트에 대한 CALLBACK 함수를 등록시켜놓고 쓰는..

JAVA SDK 1.4 많이 쓰였지만 JAVA에서는 표준이 아니라고 했는데

거의 표준 처럼 쓰이고 있다.

심지어 SUNOS의 디버거 비활성화된 옵션에 이미 선언되어있더라능..

모르던걸 알게되어 기분이 좋구만

 

관심 있으신분들은 다음 URL 참조

http://java.sun.com/j2se/1.4.2/docs/guide/jvmpi/jvmpi.html

http://saltfactory.textcube.com/entry/eclipse-remote-debugging-설정