지난주 만난 황당 사례입니다.

Webtob와 JEUS를 사용하는 사이트에 WebSquare를 설치하는데 xml, js, gif, jpg와 같은 기본적인 리소스를 전혀 못가져오는 문제가 발생했습니다. (HTTP 404오류 발생)

여러가지 테스트를 하다가 발견한 사실은 http.m파일에 mime type을 설정하면 해당 파일은 가져오고 있었습니다.

설정파일은 대략 아래와 같은 형태였습니다.



*DOMAIN
jeuservice

*NODE
testsvr WEBTOBDIR="C:/jeus32/WebtoB",
SHMKEY = 54000,
DOCROOT="C:/jeus32/webhome/servlet_home/webapps",
PORT = "8081",
LOGGING = "log1",
ERRORLOG = "log2",
JSVPORT = 9900,
IndexName="index.html,index.htm",
Options = "+Index",
        DirIndex = "Index",
HTH = 1

*SVRGROUP
htmlg NODENAME = testsvr, SvrType = HTML
#cgig NODENAME = testsvr, SVRTYPE = CGI
#ssig NODENAME = testsvr, SVRTYPE = SSI
jsvg NODENAME = testsvr, SVRTYPE = JSV

*SERVER
html SVGNAME = htmlg, MinProc = 1, MaxProc = 2
#cgi SVGNAME = cgig, MinProc = 1, MaxProc = 2
#ssi SVGNAME = ssig, MinProc = 1, MaxProc =2
MyGroup SVGNAME = jsvg, MinProc = 25, MaxProc = 25

*URI
uri Uri = "/",Svrtype = JSV

*ALIAS


*DIRINDEX
Index Options = "FANCY"

*LOGGING
log1 Format = "DEFAULT", FileName = "C:/jeus32/WebtoB/log/access.log", Option = "sync"
log2 Format = "ERROR", FileName = "C:/jeus32/WebtoB/log/error.log", Option = "sync"

*EXT
htm MimeType = "text/html", SvrType = HTML
xml MimeType = "text/xml", SvrType = HTML
xsl MimeType = "text/xml", SvrType = HTML



어떤 부분이 문제였을까요???

더보기


일부러 그렇게 설정하진 않았겠죠?

Posted by thinknote
기본적으로 ServeltInputStream은 request.getParamter과 비슷한 기능을 하는API (getParameterNames(), getParameterValues(), getParameterMap() )를 호출하면 null을 반환합니다.

getParameter와 비슷한 API를 호출하지 않은 경우에도 이러한 현상이 발생하는 경우가 있는데, 경험에 의하면 Jeniffer와 같은 APM솔루션에서 HTTP Stream을 검사하는 경우 발생하고 있습니다. (T모사에서 발생하였음.)

제니퍼 홈페이지에도 비슷한 내용이 있고요. http://www.jennifersoft.com/apm-jennifer-support-qa/forum/page/9/show/ko/3389/show.html

해결 하는 방법은

1. 제니퍼의 해당 옵션을 끈다.
2. x-www-form-urlencoded 방식으로 encoding하지 않는다.
3. WAS에서 getParameter을 읽은 후에도 ServetInputStream을 읽을 수 있게 해주는 옵션을 활성화 시킨다. (모든 WAS에서 제공하지는 않을 것 같지만, WebSphere에는 있다고 합니다. 하지만  성능 저하는 있겠죠..)
4. ServeltInputStream을 사용하지 않는다. Encoding문제를 해결하기 위해서 stream을 직접 읽었다면. Base64와 같은 다른 인코딩 방법을 쓰는 것도 좋은 해결책 입니다.





Posted by thinknote
LinkedHashMap을 사용하면 됩니다.

아래는 인터넷에서 찾은 예제 입니다. (http://www.java-examples.com/iterate-through-values-java-linkedhashmap-example )


import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Iterator;
 
public class IterateValuesOfLinkedHashMapExample {
 
  public static void main(String[] args) {
 
    //create LinkedHashMap object
    LinkedHashMap lHashMap = new LinkedHashMap();
 
    //add key value pairs to LinkedHashMap
    lHashMap.put("1","One");
    lHashMap.put("2","Two");
    lHashMap.put("3","Three");
 
    /*
      get Collection of values contained in LinkedHashMap using
      Collection values() method of LinkedHashMap class
    */
    Collection c = lHashMap.values();
 
    //obtain an Iterator for Collection
    Iterator itr = c.iterator();
 
    //iterate through LinkedHashMap values iterator
    while(itr.hasNext())
      System.out.println(itr.next());
  }
}

Posted by thinknote

WebLogic에서 JNDI로 Connection을 가져오려고 할 때 다음과 같은 에러가 발생하였다.

java.lang.ClassCastException: weblogic.jndi.internal.WLEventContextImpl incompatible with javax.sql.DataSource:weblogic.jndi.internal.WLEventContextImpl incompatible with javax.sql.DataSource
    at com.inswave.system.xda.ConnectionPool.getConnection(ConnectionPool.java:158)
    at com.inswave.system.xda.DefaultXDAImpl.getXDAConnection(DefaultXDAImpl.java:1761)
    at com.inswave.system.xda.DefaultXDAImpl.execute(DefaultXDAImpl.java:384)
    at com.inswave.system.xda.DefaultXDAImpl.execute(DefaultXDAImpl.java:349)
    at com.inswave.system.xda.DefaultXDAImpl.execute(DefaultXDAImpl.java:345)

해당 로직은 다음과 같다.

 Properties p = new Properties();
 p.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
 Context ctx = new InitialContext(p);
 ds = (DataSource) ctx.lookup(source);

원인을 찾기 위해 구글들 뒤지고 다양한 소스를 비교하는 노력을 했는데, 원인은 어이없게도 source에 Empty String("")이 들어와서 발생한 것이었다. 에러가 추정이 힘든 형태로 나타나서 쉽게 해결할 수 없었다.

Posted by thinknote

Java 프로그램은 처음 시작할 때 사용할 Heap메모리 영역을 할당하고 시작한다. 만일 이러한 Heap영역이 부족하게 되면 OutOfMemoryError이라는 에러를 발생시키고 비정상 종료하게 된다.

이러한 OutOfMemoryError이 발생할 경우 개발자는 난감한 상황에 빠지게 되는데, 어떤 프로그램에서 메모리를 많이 잡아 먹었는지 파악하기 힘들기 때문이다.

이러한 경우 일반적으로 사용되는 도구가 Profiler인데, Profiler의 가장 큰 문제는 사용하게 되면 실행 속도가 10배 이상 느려져서 정상적으로 동작하기 힘들어 진다. 그래서 Profiler를 사용하게 되면 OutOfMemoryError이 발생하지 않는 경우도 종종 있다.

그러면 이러한 OutOfMemoryError를 좀더 현실적으로 진단할 방법은 무엇일까?

1. IBM JVM의 경우

IBM JVM을 사용해 보신 분들을 이미 알고 있겠지만 IBM에서는 Heapdump라는 메모리의 Heap정보를 파일로 추출하는 기능을 제공해 준다. (필자의 경우 IBM JVM의 가장 큰 장점이라고 생각하는 기능이다.)

AIX와 같은 IBM OS에서 프로그램을 실행시켜 보신 분들은 아래와 같은 파일이 생성된 것을 볼 수 있었을 것이다.

확장자가 phd로 끝나는 파일이 Heapdump파일이고 javacore로 시작되는 파일이 Threaddump 파일이다.

Threaddump는 파일을 읽는 방법만 익히면 어렵지 않게 읽을 수 있다. 관련 내용은 추후에 다른 포스트에서 설명하겠다.

하지만 Heapdump파일의 경우 별도의 Tool이 필요하다. IBM은 HeapRoots라는 명령창 기반 프로그램과 HeapAnalyzer라는 GUI기반 프로그램을 제공한다.

HeapRoots의 경우 복잡하지는 않지만 별도의 사용법을 알아야 하지만 HeapAnalyser의 경우 직관적인 UI가 제공되서 쉽게 사용이 가능하다.

각 프로그램은 다음 링크에서 다운 받을 수 있다.

Heap Analyzer를 실행한 화면은 아래와 같다.

 

2. SUN JVM의 경우

SUN JVM의 경우 Heapdump를 따로 제공하지 않았었다. 정석은 Profiler를 이용하는 것인네 너무 느리기 때문에 여러가지 편법들이 난무했는데, 그 중에는 IBM Heapdump 모듈을 이용하는 편법도 있었다.

IBM HeapDump for Solaris : http://www.skywayradio.com/tech/WAS51/IBMHeapDump/ 

하지만 JDK 1.4.1에서만 동작하고 1.4.2이후 버전에서는 동작하지 않는다는 치명적인 단점이 있었다.

시간이 지나 Java 5가 나오면서 SUN도 운영 관리의 중요성에 눈을 조금씩 떠가면서 JMX 가 추가되었고 HotSpot VM 명령 옵션에 Heapdump를  추출하는 옵션을 추가하게 되었다.

옵션은 다음과 같다.

-XX:+HeapDumpOnOutOfMemoryError

이 옵션은 1.5.0_07 이나 1.4.2_12 이상 버전 부터 지원한다.

옵션을 추가해서 실행하면 아래와 같이 나온다.

$ java -XX:+HeapDumpOnOutOfMemoryError -mn256m -mx512m ConsumeHeap
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2262.hprof ...
Heap dump file created [531535128 bytes in 14.691 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at ConsumeHeap$BigObject.(ConsumeHeap.java:22)
        at ConsumeHeap.main(ConsumeHeap.java:32)

파일은 현재 폴더에 HPROF 바이너리 포멧으로 생성되고 JHAT(Java Heap Analysis Tool)와 같이 HPROF 포멧을 지원하는 툴로 분석할 수 있다.

JHAT : http://java.sun.com/javase/6/docs/technotes/tools/share/jhat.html

만일 폴더를 변경하려면 -XX:HeapDumpPath= 옵션을 이용할 수 있다.

관련 링크 : http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gdaog.html#gbzrr

                  http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gblfj.html

3. HP JVM의 경우

SUN JVM과 동일한데 1.5.0_04 나 1.4.2_11부터 지원한다. 상세한 내용은 아래 링크를 참고한다.

Java Troubleshooting Guide for HP-UX Systems : http://docs.hp.com/en/5992-1918/5992-1918.pdf

분석하는데 HPJtune를 사용한다.

HPjtune :  http://www.hp.com/products1/unix/java/java2/hpjtune/

Posted by thinknote

Context 와 ThreadLocal

Java 2007.12.06 00:00

Web 프로그램을 구현할 때 시작시점에 디버그 플래그를 세팅하면 해당 프로그램이 수행되는 모든 지점에서 로그를 출력하려면 어떻게 해야 할까?
J2EE에서 DB와 같은 리소스를 연동하고 트랜잭션을 관리하기 위해서 J2EE에서는 Context라는 객체를 활용한다. 이러한 Contex는 리소스에 대한 위치 투명성을 제공하고 Two Phase Commit과 같은 분산 트랜잭션을 위한 기본적인 환경을 제공한다.
이러한 Context의 기능을 이용해서 작업을 추적해서 로그를 출력하는 모듈을 개발할 수 있다. 그러면 이러한 Context를 간단하게 구현하려면 어떻게 해야 할까?
이러한 문제를 해결하려면 문제를 조금 다른 관점에서 살펴보는 안목이 필요하다.
Web 프로그램은 Muiti Thread 환경에서 실행되는데 프로그램이 각각의 Thread에서 실행된다.
이러한 각각의 Thread에 변수를 저장하는 방법이 있다면 Context와 비슷한 기능을 하는 모듈을 작성할 수 있다
그리고 이러한 모듈을 활용해서 앞에서 제시한 문제를 해결할 수 있다.
그러면 어떻게 Thread에 변수를 저장할 수 있을까? 바로 java.lang.ThreadLocal이라는 Class를 활용해서 작성할 수 있다.
ThreadLocal은 각각의 Thread별로 변수를 생성할 수 있는 기능을 제공하고 또한 각각의 Thrad에서 접근할 수 있는 API를 제공해 준다. 이러한 ThreadLocal을 이용해서 Thread에 따라 구분되는 Data를 저장하고 활용할 수 있다.

아래는 간단한 ThreadLocal 예제이다.
public class ThreadLocalSample {
    public static void main(String[] args) {
        ThreadLocal local = new ThreadLocal(); 
        local.set("Thread Local변수");
        System.out.println("ThreadLocal변수 : " + local.get() );
    }
}

위와 같이 일반적인 Class와 동일하게 객체를 생성하고 값을 설정하고 읽어온다.
다만 내부적으로 Thread.currentThread()라는 함수로 현재 Thread 객체를 찾아와서 객체를 기준으로 Map에서 값을 읽어오도록 구현이 되어 있다. J2SE 1.4.1의 ThreadLocal의 get()과 set()을 살펴보면 아래와 같다.

public Object get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        return map.get(this);
    // Maps are constructed lazily.  if the map for this thread
    // doesn't exist, create it, with this ThreadLocal and its
    // initial value as its only entry.
    Object value = initialValue();
    createMap(t, value);
    return value;
}

public void set(Object value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

여기서 중요한 부분은 Thread.currentThread()를 이용하는 부분이다. 사실 Thread.currentThread() 함수를 이용하면 ThradLocal을 어렵지 않게 직접 구현할 수도 있다.

그러면 이러한 ThreadLocal을 이용해서 디버그 flag를 저장하고 읽어오는 함수를 구현해 보자.

public class DebugContext {
    // singleton 객체 생성
    private static DebugContext context = new DebugContext();
    private ThreadLocal info = new ThreadLocal();
   
    private DebugContext() {
    }
   
    // singleton객체 반환 함수
    public static DebugContext getContext() {
        return context;
    }
   
    public void setDebug( boolean debug ) {
        info.set( debug + "" );
    }
   
    public boolean getDebug() {
        return Boolean.valueOf( info.get() ).booleanValue();
    }
}

그러면 이 함수를 이용해서 로깅을 하는 함수를 구현해 보자. 로깅은 단순하게 System.out.println을 이용하겠다.

public class Logger {
    public static void printLog( String s ) {
        if( DebugContext.getContext().getDebug() ) {
            System.out.println( s );
        }
    }
}

테스트 시나리오에서 A.jsp라는 페이지에서 BizTask.getInfo()를 호출하고 BizTask.getInfo()에서는 DAO.select()라는 class를 호출한다고 가정하겠다. 이경우 A.jsp와 두개의 Class에서 출력되는 로그를 한번에 Enable/Disable하기 위해서 위에서 만든 Logger와 DebugContext를 이용해 보겠다.

A.jsp는 아래와 같다

<%
String debug = request.getParameter("debug");
if( debug != null && debug.equals("true") {
 DebugContext.getContext().setDebug( true );
} else {
 DebugContext.getContext().setDebug( false );
}

Logger.printLog( "현재 Thread에서 실행되는 모든 로그 출력 시작");
BizTask task = new BizTask();
Logger.printLog( "BizTask.getInfo() 호출 시작");
task.getInfo();
Logger.printLog( "BizTask.getInfo() 호출 완료");
Logger.printLog( "현재 Thread에서 실행되는 모든 로그 출력 종료");
DebugContext.getContext().setDebug( false ); //
Logger.printLog( "로그 테스트");
%>

BizTask.java는 아래와 같다.

public class BizTask{
 public BizTask() {
  Logger.printLog("BizTask 객체 생성");
 }
 
 public void getInfo() {
  DAO dao = new DAO();
  Logger.printLog("DAO.select() 호출 시작");
  dao.select();
  Logger.printLog("DAO.select() 호출 완료");
 }
}

DAO.java는 아래와 같다.

 public class DAO{
 public DAO() {
  Logger.printLog("DAO 객체 생성");
 }
 
 public void select() {
  Logger.printLog("DAO.select() 실행");
 }
}

A.jsp?bebug=true 라는 주소로 호출하면 아래와 같은 로그가 나오게 된다.

현재 Thread에서 실행되는 모든 로그 출력 시작
BizTask 객체 생성
BizTask.getInfo() 호출 시작
DAO 객체 생성
DAO.select() 호출 시작
DAO.select() 실행
DAO.select() 호출 완료
BizTask.getInfo() 호출 완료
현재 Thread에서 실행되는 모든 로그 출력 종료

A.jsp아래쪽에 있는 로그 테스트 라는 부분은 이미 Context에서 debug플래그가 제거되었기 때문에 출력되지 않는다. 또한 A.jsp?debug=false로 호출하면 아무런 로그도 출력되지 않는다.

이렇게 해서 Context를 구현하는 핵심 방법인 ThreadLoca을 알아보고 ThreadLocal을 이용해서 특정 Thread만 debug flag를 활성화 시키는 방법을 알아보았다.

아마 코딩 경험이 있으신 분들은 눈치 채었겠지만 ThreadLocal을 이용하는 방법은 참 다양하다. 
예를 들어 JDBC Connection을 개발자가 반환하는 것이 아니라 응용프로그램이 종료될 때 일괄로 반환해 주는 기능을 많은 Framework에서 제공하는데 이러한 기능도 ThreadLocal을 이용한다.

Posted by thinknote