@Value 어노테이션으로 application.properties의 값을 가져올 수 있습니다. 

 

@Value 어노테이션은 Spring boot에서 제공하는 어노테이션입니다. 빈(bean)이 생성되는 시점에 

값을 할당하기 위해 사용됩니다. 

 

일반적으로 개발, 품질, 운영서버와 같이 환경 값이 변경되는 경우 application.properties에 설정된 값을 사용하기 위해 사용되기도 합니다. 

 

아래와 같이 application.properties에 값이 설정되어 있을 때

#EAI Call Url
eai.call.url=http://abc.abccdd.com/restapi/

 

@Value 어노테이션을 이용하여 갑을 주입하는 방법은 아래와 같습니다.

@Value("${eai.call.url}")
private String eaiUrl;

 

eaiUrl이라는 String 변수에 http://abc.abccdd.com/restapi/ 값이 담기게 됩니다. 

 

주의할 점은 변수 선언시 static으로 선언하면 값이 주입되지 않습니다. 

이클립스 SVN 사용시 특정 폴더까지 관리가 되어 거슬리는 경우 패턴을 지정하여 해당 패턴데 대한 파일들을 동기화

하지 않을 수 있습니다.

 

예를 들어 log 파일은 동기화 하지 않는경우는

*.log

를 등록하여 동기화를 제외할 수 있습니다.

 

Window > Preferences

 

Preferences > Team > Ignored Resources 에서 Add Pattern 하여 제외 패턴을 등록합니다.

방법을 설명하기에 앞서 가장먼저 말씀드리자면 Apache Tomcat 8.0.30에서는 작동하지만 Tomcat 8.5에서는 작동하지 않습니다. 아마도 아래와 같은 에러로그를 만날 수 있을 겁니다.

Java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
	at org.Apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.Java:467)
	at org.Apache.coyote.http11.Http11Processor.service(Http11Processor.Java:667)
	at org.Apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.Java:66)
    ......
    ....
    ...
    ..
    .
    

 

8.5버전 이전까지는 아래의 매개 변수를 이용하여 사용할 수 없는 파라메터에 대해서 정의할 수 있었습니다.

Tomcat.util.http.parser.HttpParser.requestTargetAllow 

 

8.5버전 부터는 해당 매개변수를 지원하지 않아 Sever.xml의 Connector 설정에서 별도의 파라메터인 relaxedQueryChars를 통해 설정 할 수 있습니다. 

<작성예시>
<Connector port="80" 
           protocol="HTTP/1.1"
           maxThreads="150"
           connectionTimeout="20000"
           redirectPort="443"
           compression="on"
           compressionMinSize="2048"
           noCompressionUserAgents="gozilla, traviata"
           compressableMimeType="text/html,text/xml"
           
           relaxedQueryChars="[,]"
/>

응용 프로그램에 기본적으로 Tomcat에서 지원하지 않는 특수 문자가 더 필요한 경우 relaxedQueryChars 속성에 해당 특수 문자를 쉼표로 구분하여 추가 할 수 있습니다.

 

 

마치며...

파이프 기호는 시간이 지남에 따라 브라우저마다 다르게 처리 된 기호입니다. 예를 들어, Chrome 및 Firefox는 복사/붙여 넣기시 파이프가있는 URL을 다르게 변환합니다.  따라서 파이프기호는 사용을 지양하는것이 오류를 줄일 수 있고 우리가 시간을 절약할 수 있는 길입니다.

Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener

 

 

톰켓 실행할때 해당 오류를 만날 수 있습니다. 

 

이유는 간단합니다. 라이브러리에 runtime 라이브러리가 추가되지 않아서 발생한 Exception 입니다.

 

 

 

 

 

 

 

일반적으로 이미지 타입이 RGB가 아닌 CMYK인 경우 


ImageIO.read() 를 이용하여 읽을 경우  Unsupported Image Type 가 발생한다.


일반적으로 CMYK타입 이미지는  ImageIO 로 read 하기 불가능하다




ImageIO.read(file); 를 이용해서 Exception이 발생할 경우 컨버터를 이용하여 처리하는 방식을 이용하여 오류를 줄일 수 있다. 



converter_Lib.zip



converter_Lib.zip의 icc 파일은 Cmyk2RgbConverter와 동일한 경로에 위치하면 된다. 



< Cmyk2RgbConverter.java >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public class Cmyk2RgbConverter {
 
    public static final int TYPE_RGB = 1;
    public static final int TYPE_CMYK = 2;
    public static final int TYPE_YCCK = 3;
 
    private static int colorTp = TYPE_RGB;
    private static boolean markerFlag = false;
 
 
 
    public static BufferedImage readImage(File file) throws IOException, ImageReadException {
        colorTp = TYPE_RGB;
        markerFlag = false;
 
        ImageInputStream stream = ImageIO.createImageInputStream(file);
        Iterator < itor_imgReader > iter = ImageIO.getitor_imgReaders(stream);
        while (iter.hasNext()) {
            itor_imgReader reader = iter.next();
            reader.setInput(stream);
 
            BufferedImage image;
 
            ICC_pf pf = null;
            try {
                image = reader.read(0);
                
                reader.reset();
            } catch (IIOException e) {
                colorTp = TYPE_CMYK;
                checkMarker(file);
                pf = Sanselan.getICCpf(file);
                Writablewr wr = (Writablewr) reader.readwr(0null);
 
                
                reader.reset();
 
                if (colorTp == TYPE_YCCK) {
                    convertYcck2Cmyk(wr);
                }
 
                if (markerFlag) {
                    convInvColors(wr);
                }
                image = convertCmykToRgb(wr, pf);
                image.flush();
            } finally {
                stream.close(); 
            }
 
            return image;
        }
 
        return null;
    }
 
    public static void checkMarker(File file) throws IOException, ImageReadException {
        JpegImageParser parser = new JpegImageParser();
        ByteSource byteSource = new ByteSourceFile(file);
        @SuppressWarnings("rawtypes")
        ArrayList segments = parser.readSegments(byteSource, new int[] {
            0xffee
        }, true);
        if (segments != null && segments.size() >= 1) {
            UnknownSegment app14Segment = (UnknownSegment) segments.get(0);
            byte[] data = app14Segment.bytes;
            if (data.length >= 12 && data[0== 'A' && data[1== 'd' && data[2== 'o' && data[3== 'b' && data[4== 'e') {
                markerFlag = true;
                int transform = app14Segment.bytes[11& 0xff;
                if (transform == 2)
                    colorTp = TYPE_YCCK;
            }
        }
    }
 
    public static void convertYcck2Cmyk(Writablewr wr) {
        int height = wr.getHeight();
        int width = wr.getWidth();
        int stride = width * 4;
        int[] rowData = new int[stride];
        for (int h = 0; h < height; h++) {
            wr.getPixels(0, h, width, 1, rowData);
 
            for (int x = 0; x < stride; x += 4) {
                int y = rowData[x];
                int cb = rowData[x + 1];
                int cr = rowData[x + 2];
 
                int c = (int)(y + 1.402 * cr - 178.956);
                int m = (int)(y - 0.34414 * cb - 0.71414 * cr + 135.95984);
                y = (int)(y + 1.772 * cb - 226.316);
 
                if (c < 0)
                    c = 0;
                else if (c > 255)
                    c = 255;
                if (m < 0)
                    m = 0;
                else if (m > 255)
                    m = 255;
                if (y < 0)
                    y = 0;
                else if (y > 255)
                    y = 255;
 
                rowData[x] = 255 - c;
                rowData[x + 1= 255 - m;
                rowData[x + 2= 255 - y;
            }
 
            wr.setPixels(0, h, width, 1, rowData);
        }
    }
 
    public static void convInvColors(Writablewr wr) {
        int height = wr.getHeight();
        int width = wr.getWidth();
        int stride = width * 4;
        int[] rowData = new int[stride];
        for (int h = 0; h < height; h++) {
            wr.getPixels(0, h, width, 1, rowData);
            for (int x = 0; x < stride; x++)
                rowData[x] = 255 - rowData[x];
            wr.setPixels(0, h, width, 1, rowData);
        }
    }
 
    public static BufferedImage convertCmykToRgb(wr cmykwr, ICC_pf cmykpf) throws IOException {
 
        if (cmykpf == null) {
            cmykpf = ICC_pf.getInstance(CMYKConverter.class.getResourceAsStream("ISOcoated_v2_300_eci.icc"));
        }
 
        ICC_ColorSpace cmykCS = new ICC_ColorSpace(cmykpf);
        BufferedImage rgbImage = new BufferedImage(cmykwr.getWidth(), cmykwr.getHeight(), BufferedImage.TYPE_INT_RGB);
        Writablewr rgbwr = rgbImage.getwr();
        ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
        ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
        cmykToRgb.filter(cmykwr, rgbwr);
        return rgbImage;
    }
 
    public static void intToBigEndian(int value, byte[] array, int index) {
        array[index] = (byte)(value >> 24);
        array[index + 1= (byte)(value >> 16);
        array[index + 2= (byte)(value >> 8);
        array[index + 3= (byte)(value);
    }
 
}
 
cs


우선 Tomcat 의 경우 실행할때에 옵션을 줄수있습니다.

다만 이 옵션이 Tomcat 을 실행할때 다른 프로세스와 다르게 CATALINA_OPTS 라는 변수로 추가를 해줘야 합니다.

방법은 정해져 있지 않습니다. Tomcat 설치 디렉토리의 bin 폴더 밑에 catalina.sh 에

추가해주거나 접속한 계정의 홈 디렉토리에 있는 .bash_profile 이나 /etc/profile 에 추가해줘도 전혀 문제 없습니다.

(Tomcat 실행시 CATALINA_OPTS 라는 변수를 참고해서 실행하기에 그렇습니다.)

 

우선 Tomcat 의 CATALINA 옵션에 대해 알아보겠습니다.

- server : Server HotSpot JVM을 사용하는 옵션입니다. Server HotSpot JVM은 Desktop용 Appkication을
구동하는데 유리하고, 최적화(Optimization)에 필요한 모든 과정을 최대한으로 수행합니다.
Application의 시작시간은 느리지만, 일정 시간이 흐르면 Client HotSpot JVM에 
비해 훨씬 뛰어난 성능을 보장합니다.  
※ Jdk 1.5부터는 Server-Class머신인 경우에는 -server 옵션이 기본값이며,
 Server-Class머신이란 2장 이상의 CPU와 2G이상의 메모리를 갖춘 머신을 의미합니다.  
 
 - Xms  : Java Heap의 최소 크기값을 지정하는 부분입니다. 
          Java Heap Size는 -Xms 옵션으로 지정한 크기로 시작하며 최대 -Xmx옵션으로 지정한 크기만큼 
          증가합니다.  
 
 - Xmx  : Java Heap의 최대 크기값을 지정하는 부분입니다. -Xms 옵션으로 지정한 크기로 시작하며 
          최대 -Xmx옵션으로 지정한 크기만큼  증가합니다.   
          ※ Sun HotSpt JVM 계열에서는 최초 크기와 최대 크기를 동일하게 부여할 것을 권장하고 있으며, 
             크기의 동적인 변경에 의한 오버 헤들를 최소화하기 위해서입니다.  
- XX:NewSize=  : New Generation의 시작 크기를 지정값 입니다.  
- XX:MaxNewSize=  : New Generation의 최대 크기를 지정값 입니다.  
※ New Generation의 크기는 NewSize옵션값과 MaxNewSize옵션값에 의해 결정됩니다   

- XX:PermSize=  : Permanent Generation의 최초 크기를 지정하는 값입니다.   
- XX:MaxPermSize=  : Permanent Generation의 최대 크기를 지정하는 값입니다.  
※ 많은 수의 Class를 사용하는 Application들은 Permanent Generation의 크기가 
   작을 경우 Out of Memory Error가 발생하며 Class를 로딩하지 못하거나 사용중 
   다운되는 경우 때문에 초기 Permanent Generation의 값을 메모리에 여유가 있다면 
   넉넉하게 주는 것이 좋습니다.
- XX:NewRatio=  : New Generation과 Old Generation의 비율을 결정합니다.
- XX:SurvivorRatio=  : Survivor Space와 Eden Space의 비율을 지정하는 값입니다. 
                       만일 이 값이 6이면,  To Survivor Ratio:From Survivor Ratio:Eden Space = 1:1:6 이 됩니다.  
                       즉, 하나의 Survivor Space의 크기가 New Generation의 1⁄8 이 된다. Survivor Space의   
                       크기가 크면 Tenured Generation으로 옮겨가기 전의 중간 버퍼 영역이 커지는 게됩니다.    
                       따라서 Full GC의 빈도를 줄이는 역할을 할 수 있는 반면 Eden Space의 크기가 줄어들므로 
                       Mirror GC가 자주 발생하게 될 가능성이 있습니다.  
- XX:ReservedCodeCacheSize=  : Code Cache의 최대 사이즈의 크기(byte) 설정값 입니다.  
- XX:+DisableExplicitGC  : System.gc 호출에 의한 Explicit GC를 비활성화하며, RMI에 의한 
                           Explicit GC나   Applicaton에서의 Explicit GC를 원천적으로 
                           방지하고자 할 경우에 사용됩니다.  
- XX:+UseConcMarkSweepGC  : CMS Collector를 사용할 지의 여부를 지정하는 옵션이며, 
                            GC Pause에 의한 사용자  응답 시간 저하 현상을 줄이고자 할 경우에
                            사용이 권장됩니다.  
- XX:+AggressiveOpts  : 최신 HotSpot VM 성능을 최적화하는 옵션입니다.  
- Djava.net.preferIPv4Stack  : IPv4인식하기 위해 사용합니다.  
- Djava.awt.headless  : 비윈도우 환경에서 GUI 클래스를 사용할 수 있게 하는 옵션입니다.

해서.. 저의 경우 /etc/profile 에 JAVA_HOME 과 같이 CATALINA_OPTS 변수를 추가해서 아래의 처럼 사용 중입니다.

CATALINA_OPTS="-server -Xms2048M -Xmx2048M
-XX:PermSize=128M 
-XX:MaxPermSize=128M 
-Xnoclassgc -XX:NewSize=512M
-XX:MaxNewSize=1024M
-XX:+UseParNewGC 
-XX:ParallelGCThreads=4
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=50
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC 
-XX:+AggressiveOpts 
-Djava.net.preferIPv4Stack=true 
-Djava.awt.headless=true"

※ 출처 : http://soul.tistory.com/63

 


 

 

 

Apache Tomcat Tuning Guide

 

이번에는 톰캣 서버에 대한 튜닝 옵션에 대해서 한번 알아보자.

애플리케이션 관점에서의 튜닝도 중요하지만, 각 솔루션에 대한 특성을 업무 시나리오에 맞춰서 튜닝하는 것도 못지 않게 중요하다. 여기서 톰캣 튜닝을 설명하는 것은 톰캣 자체에 대한 튜닝 옵션을 소개하는 것도 목적이 있지만, 그보다 업무형태에 따라서 어떠한 접근을 해서 톰캣을 튜닝하는지를 소개하기 위함이다.

 

가정

여기서 튜닝 하는 톰캣은 HTTP/JSON형태의 REST 형태로 서비스를 제공하는 API 서버의 형태이다. 여러대의 톰캣을 이용하여 REST 서비스를 제공하며, 앞단에는 L4 스위치를 둬서 부하를 분산하며, 서비스는 stateless 서비스로 공유되는 상태 정보가 없다. 

 

server.xml 튜닝

톰캣의 대부분 튜닝 패러미터는 ${Tomcat_HOME}/conf/server.xml 파일에 정의된다.

몇몇 parameter를 살펴보도록 하자.

 

Listener 설정

 <Listener className="org.apache.catalina.security.SecurityListener" checkedOsUsers="root" /> 

이 옵션은 tomcat이 기동할 때, root 사용자이면 기동을 하지 못하게 하는 옵션이다. 서버를 운영해본 사람이라면 종종 겪었을 실수중의 하나가application server를 root 권한으로 띄웠다가 다음번에 다시 실행하려고 하면 permission 에러가 나는 시나리오 이다. root 권한으로 서버가 실행되었기 때문에, 각종 config 파일이나 log 파일들의 permission이 모두 root로 바뀌어 버리기 때문에, 일반 계정으로 다시 재 기동하려고 시도하면, config 파일이나 log file들의 permission 이 바뀌어서 파일을 읽어나 쓰는데 실패하게 되고 결국 서버 기동이 불가능한 경우가 있다. 이 옵션은 이러한 실수를 막아 줄 수 있다.

 

Connector 설정

 

protocol="org.apache.coyote.http11.Http11Protocol"

먼저 protocol setting인데, Tomcat은 네트워크 통신하는 부분에 대해서 3가지 정도의 옵션을 제공한다. BIO,NIO,APR 3 가지이다. NIO는 Java의 NIO 라이브러리를 사용하는 모듈이고, APR은 Apache Web Server의 io module을 사용한다. 그래서 C라이브러리를 JNI 인터페이스를 통해서 로딩해서 사용하는데, 속도는 APR이 가장 빠른것으로 알려져 있지만, JNI를 사용하는 특성상, JNI 코드 쪽에서 문제가 생기면, 자바 프로세스 자체가 core dump를 내면서 죽어 버리기 때문에 안정성 측면에서는 BIO나 NIO보다 낮다. BIO는 일반적인 Java Socket IO 모듈을 사용하는데, 이론적으로 보면 APR > NIO > BIO 순으로 성능이 빠르지만, 실제 테스트 해보면 OS 설정이나 자바 버전에 따라서 차이가 있다. Default는 BIO이다.

 

acceptCount="10"

이 옵션은 request Queue의 길이를 정의한다. HTTP request가 들어왔을때, idle thread가 없으면 queue에서 idle thread가 생길때 까지 요청을 대기하는 queue의 길이이다. 보통 queue에 메세지가 쌓였다는 것은 해당 톰캣 인스턴스에 처리할 수 있는 쓰레드가 없다는 이야기이고, 모든 쓰레드를 사용해도 요청을 처리를 못한다는 것은 이미 장애 상태일 가능성이 높다.

그래서 큐의 길이를 길게 주는 것 보다는, 짧게 줘서, 요청을 처리할 수 없는 상황이면 빨리 에러 코드를 클라이언트에게 보내서 에러처리를 하도록 하는 것이 좋다. Queue의 길이가 길면, 대기 하는 시간이 길어지기 때문에 장애 상황에서도 계속 응답을 대기를 하다가 다른 장애로 전파 되는 경우가 있다.

순간적인 과부하 상황에 대비 하기 위해서 큐의 길이를 0 보다는 10내외 정도로 짧게 주는 것이 좋다.

 

enableLookups="false"

톰캣에서 실행되는 Servlet/JSP 코드 중에서 들어오는 http request에 대한 ip를 조회 하는 명령등이 있을 경우, 톰캣은 yahoo.com과 같은 DNS이름을 IP주소로 바뀌기 위해서 DNS 서버에 look up 요청을 보낸다. 이것이 http request 처리 중에 일어나는데, 다른 서버로 DNS 쿼리를 보낸다는 소리이다. 그만큼의 서버간의 round trip 시간이 발생하는데, 이 옵션을 false로 해놓으면 dns lookup 없이 그냥 dns 명을 리턴하기 때문에, round trip 발생을 막을 수 있다.

 

compression="off"

HTTP message body를 gzip 형태로 압축해서 리턴한다. 업무 시나리오가 이미지나 파일을 response 하는 경우에는  compression을 적용함으로써 네트워크 대역폭을 절약하는 효과가 있겠지만, 이 업무 시스템의 가정은, JSON 기반의 REST 통신이기 때문에, 굳이 compression을 사용할 필요가 없으며, compression에 사용되는 CPU를 차라리 비지니스 로직 처리에 사용하는 것이 더 효율적이다.

 

maxConnection="8192"

하나의 톰캣인스턴스가 유지할 수 있는 Connection의 수를 정의 한다.

이 때 주의해야 할 점은 이 수는 현재 연결되어 있는 실제 Connection의 수가 아니라 현재 사용중인 socket fd (file descriptor)의 수 이다. 무슨 말인가 하면 TCP Connection은 특성상 Connection 이 끊난 후에도 바로 socket이 close 되는 것이 아니라 FIN 신호를 보내고, TIME_WAIT 시간을 거쳐서 connection을 정리한다. 실제 톰캣 인스턴스가 100개의 Connection 으로 부터 들어오는 요청을 동시 처리할 수 있다하더라도, 요청을 처리하고 socket이 close 되면 TIME_WAIT에 머물러 있는 Connection 수가 많기 때문에, 단시간내에 많은 요청을 처리하게 되면 이TIME_WAIT가 사용하는 fd 수 때문에, maxConnection이 모자를 수 있다. 그래서 maxConnection은 넉넉하게 주는 것이 좋다.

이외에도 HTTP 1.1 Keep Alive를 사용하게 되면 요청을 처리 하지 않는 Connection도 계속 유지 되기 때문에, 요청 처리 수 보다, 실제 연결되어 있는 Connection 수가 높게 된다.

그리고, process당 열 수 있는 fd수는 ulimit -f 를 통해서 설정이 된다. maxConnection을 8192로 주더라도, ulimit -f에서 fd 수를 적게 해놓으면 소용이 없기 때문에 반드시 ulimit -f 로 최대 물리 Connection 수를 설정해놔야 한다.

 

maxKeepAliveRequest="1"

HTTP 1.1 Keep Alive Connection을 사용할 때, 최대 유지할 Connection 수를 결정하는 옵션이다. 본 시나리오에서는 REST 방식으로Connectionless 형태로 서비스를 진행할 예정이기 때문에, Kepp Alive를 사용하지 않기 위해서 값을 1로 준다.

만약에 KeepAlive를 사용할 예정이면, maxConnection과 같이 ulimit에서 fd수를 충분히 지정해줘야 하낟.

 

maxThread="100"

사실상 이 옵션이 가장 중요한 옵션이 아닌가 싶다. 톰캣내의 쓰레드 수를 결정 하는 옵션이다. 쓰레드수는 실제 Active User 수를 뜻한다. 즉 순간 처리 가능한 Transaction 수를 의미한다.

일반적으로 100 내외가 가장 적절하고, 트렌젝션의 무게에 따라 50~500 개 정도로 설정하는 게 일반적이다. 이 값은 성능 테스트를 통해서 튜닝을 하면서 조정해 나가는 것이 좋다.

 

tcpNoDelay="true"

TCP 프로토콜은 기본적으로 패킷을 보낼때 바로 보내지 않는다. 작은 패킷들을 모아서 버퍼 사이즈가 다 차면 모아서 보내는 로직을 사용한다. 그래서 버퍼가 4K라고 가정할때, 보내고자 하는 패킷이 1K이면 3K가 찰 때 까지 기다리기 때문에, 바로바로 전송이 되지 않고 대기가 발생한다.

tcpNoDelay 옵션을 사용하면, 버퍼가 차기전에라도 바로 전송이 되기 때문에, 전송 속도가 빨라진다. 반대로, 작은 패킷을 여러번 보내기 때문에 전체적인 네트워크 트래픽은 증가한다. (예전에야 대역폭이 낮아서 한꺼번에 보내는 방식이 선호되었지만 요즘은 망 속도가 워낙 좋아서tcpNoDelay를 사용해도 대역폭에 대한 문제가 그리 크지 않다.)

 

 

Tomcat Lib 세팅

다음으로 자바 애플리케이션에서 사용하는 라이브러리에 대한 메모리 사용률을 줄이는 방법인데, 일반적으로 배포를 할때 사용되는 라이브러리(jar)를 *.war 패키지 내의 WEB-INF/jar 디렉토리에 넣어서 배포 하는 것이 일반적이다. 보통 하나의 war를 하나의 톰캣에 배포할 때는 큰 문제가 없는데, 하나의 톰캣에 여러개의 war 파일을 동시 배포 하게 되면, 같은 라이브러리가 각각 다른 클래스 로더로 배포가 되기 때문에, 메모리 효율성이 떨어진다.

그래서 이런 경우는 ${TOMCAT_HOME}/lib 디렉토리에 배포를 하고 war 파일에서 빼면 모든 war가 공통 적으로 같은 라이브러리를 사용하기 때문에 메모리 사용이 효율적이고, 또한 시스템 운영 관점에서도 개발팀이 잘못된 jar 버전을 패키징해서 배포하였다 하더라도, lib 디렉토리의 라이브러리가 우선 적용되기 때문에, 관리가 편하다.

반대로 war의 경우, war만 운영중에 재배포를 하면 반영이 가능하지만, lib 디렉토리의 jar 파일들은 반드시 톰캣 인스턴스를 재기동해야 반영되기 때문에, 이 부분은 주의해야 한다.

 

JVM Tuning

Java Virtual Machine 튜닝은 java 기반 애플리케이션에서는 거의 필수 요소이다.

-server

제일 먼저 해야할일은 JVM 모드를 server 모드로 전환하는 것이다. JVM 내의 hotspot 컴파일러도 클라이언트 애플리케이션이나 서버 애플리케이션이냐 에 따라서 최적화 되는 방법이 다르다.

그리고 메모리 배치 역시 클라이언트 애플리케이션(MS 워드와같은)의 경우 버튼이나 메뉴는 한번 메모리에 로드 되면, 애플리케이션이 끝날 때 까지 메모리에 잔존하기 때문에 Old 영역이 커야 하지만, 서버의 경우 request를 받아서 처리하고 응답을 주고 빠져서 소멸되는 객체들이 대부분이기 때문에, New 영역이 커야 한다.

이런 서버나 클라이언트냐에 대한 최적화 옵션이 이 옵션 하나로 상당 부분 자동으로 적용되기 때문에, 반드시 적용하기를 바란다.

 

메모리 옵션

앞에서도 설명하였듯이 JVM 튜닝의 대부분의 메모리 튜닝이고 그중에서도 JVM 메모리 튜닝은 매우 중요하다. 결국 Full GC 시간을 줄이는 것이 관건인데, 큰 요구 사항만 없다면, 전체 Heap Size는 1G 정도가 적당하다. 그리고 New대 Old의 비율은 서버 애플리케이션의 경우 1:2 비율이 가장 적절하다. 그리고 PermSize는 class가 로딩되는 공간인데, 배포하고자 하는 애플리케이션이 아주 크지 않다면 128m 정도면 적당하다. (보통256m를 넘지 않는다. 256m가 넘는다면 몬가 애플린케이션 배포나 패키징에 문제가 있다고 봐야 한다.)

그리고 heap size는 JVM에서 자동으로 늘리거나 줄일 수 가 있다. 그래서 -Xms와 -Xmx로 최소,최대 heap size를 정할 수 있는데, Server 시스템의 경우 항상 최대 사용 메모리로 잡아 놓는 것이 좋다. 메모리가 늘어난다는 것은 부하가 늘어난다는 것이고, 부하가 늘어날때 메모리를 늘리는 작업 자체가 새로운 부하가 될 수 있기 때문에, 같은 값을 사용하는 것이 좋다.

이렇게 JVM 메모리를 튜닝하면 다음과 같은 옵션이 된다.

-Xmx1024m –Xms1024m -XX:MaxNewSize=384m -XX:MaxPermSize=128m

이렇게 하면 전체 메모리 사용량은 heap 1024m (이중에서 new가 384m) 그리고 perm이 128m 가 되고, JVM 자체가 사용하는 메모리가 보통300~500m 내외가 되서 java process가 사용하는 메모리 량은 대략 1024+128+300~500 = 대략 1.5G 정도가 된다.

 

32 bit JVM의 경우 process가 사용할 수 있는 공간은 4G가 되는데, 이중 2G는 시스템(OS)이 사용하고 2G가 사용자가 사용할 수 있다. 그래서 위의 설정을 사용하면 32bit JVM에서도 잘 동작한다.

64 bit JVM의 경우 더 큰 메모리 영역을 사용할 수 있는데, 일반적으로 2G를 안 넘는 것이 좋다.(최대 3G), 2G가 넘어서면 Full GC 시간이 많이 걸리기 시작하기 때문에, 그다지 권장하지 않는다. 시스템의 가용 메모리가 많다면 Heap을 넉넉히 잡는 것보다는 톰캣 인스턴스를 여러개 띄워서 클러스터링이나 로드밸런서로 묶는 방법을 권장한다.

 

OutOfMemory

자바 애플리케이션에서 주로 문제가 되는 것중 하나가 Out Of Memory 에러이다. JVM이 메모리를 자동으로 관리해줌에도 불구하고, 이런 문제가 발생하는 원인은 사용이 끝낸 객체를 release 하지 않는 경우이다. 예를 들어 static 변수를 통해서 대규모 array나 hashmap을 reference 하고 있으면, GC가 되지 않고 계속 메모리를 점유해서 결과적으로 Out Of Memory 에러를 만들어낸다.

Out Of Memory 에러를 추적하기 위해서는 그 순간의 메모리 레이아웃인 Heap Dump가 필요한데, 이 옵션을 적용해놓으면, Out Of Memory가 나올때, 순간적으로 Heap Dump를 떠서 파일로 저장해놓기 때문에, 장애 발생시 추적이 용이하다.

-XX:-HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_pid<pid>.hprof

 

GC 옵션

다음은 GC 옵션이다. Memory 옵션 만큼이나 중요한 옵션인데, Parallel GC + Concurrent GC는 요즘은 거의 공식처럼 사용된다고 보면 된다. 이때 Parallel GC에 대한 Thread 수를 정해야 하는데, 이 Thread수는 전체 CPU Core수 보다 적어야 하고, 2~4개 정도가 적당하다.

-XX:ParallelGCThreads=2 -XX:-UseConcMarkSweepGC

GC 로그 옵션

그리고 마지막으로 GC Log 옵션이다. 서버와 JVM이 건강한지 메모리상 문제는 없는지 GC 상황은 어떻게 디는지를 추적하려면 GC 로그는 되도록 자세하게 추출할 필요가 있다. GC로그를 상세하게 걸어도 성능 저하는 거의 없다.

-XX:-PrintGC -XX:-PrintGCDetails -XX:-PrintGCTimeStamps -XX:-TraceClassUnloading -XX:-TraceClassLoading

 

마지막에 적용된 TraceClassLoading은 클래스가 로딩되는 순간에 로그를 남겨준다. 일반적으로는 사용하지 않아도 되나, OutOfMemory 에러 발생시 Object가 아니라 class에서 발생하는 경우는 Heap dump로는 분석이 불가능 하기 때문에, Out Of Memory 에러시 같이 사용하면 좋다.

 

지금까지 간략하게 나마 톰켓 솔루션에 대한 튜닝 parameter 에 대해서 알아보았다. 사실 이러한 튜닝은 일반적인 개발자에게는 힘든 일이다. 해당 솔루션에 대한 많은 경험이 있어야 하기 때문에, 이런 parameter는 vendor의 기술 지원 엔지니어를 통해서 가이드를 받고, 성능 테스트 과정에서 최적화를 하고 표준화된 parameter를 정해서 사용하는 것이 좋다. Apache Tomcat의 경우에도 오픈소스이기는 하지만, Redhat등에서 기술 지원을 제공한다.

 

※ 출처 : http://bcho.tistory.com/788

 

-- 2016-11-24 추가 ------------------------------------------------------------------

 1) Permanent space: JVM 클래스와 메소드 개체를 위해 쓰인다. 
 2) Old object space: 오래된 개체들을 위해 쓰인다. 
 3) New(young) object space: 새로 생성된 개체들을 위해 쓰인다.



 

List에 내용물이 String이나 int가 아닌 경우 정렬

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
ArrayList<HashMap<StringString>> items = new ArrayList<HashMap<StringString>>();
 
 
Collections.sort(items, new Comparator<HashMap<StringString>>() {
            @Override
            public int compare(HashMap<StringString> first, HashMap<StringString> second) {
                
                int firstValue = Integer.valueOf(first.get("ORDER_BY"));
                int secondValue = Integer.valueOf(second.get("ORDER_BY"));
 
                if (firstValue > secondValue) {
                    return -1;
                } else if (firstValue < secondValue) {
                    return 1;
                } else /* if (firstValue == secondValue) */ {
                    return 0;
                }
                
            }
        });
cs

 

 

물론 비교를 compareTo로 해도 되긴하는데 내경 우에는 700 60 500의 값이 비교될 때 

 

60이 500보다 우선순위로 정렬되는 기이현상으로 인하여 

 

if else if로 구현하였음

1       GT    GTM     과테말라        es      spa     스페인어
2       GR    GRC     그리스          el      ell     그리스어
3       ZA    ZAF     남아프리카      en      eng     영어
4       NL    NLD     네덜란드        nl      nld     네덜란드어
5       NO    NOR     노르웨이        no      nor     노르웨이어
6       NO    NOR     노르웨이        no      nor     노르웨이어
7       NZ    NZL     뉴질랜드        en      eng     영어
8       NI    NIC     니카라과        es      spa     스페인어
9       TW    TWN     대만            zh      zho     중국어
10      KR    KOR     대한민국        ko      kor     한국어
11      DK    DNK     덴마크          da      dan     덴마크어
12      DO    DOM     도미니카 공화국 es      spa     스페인어
13      DE    DEU     독일            de      deu     독일어
14      LV    LVA     라트비아        lv      lav     라트비아어(레트어)
15      RU    RUS     러시아          ru      rus     러시아어
16      LB    LBN     레바논          ar      ara     아랍어
17      RO    ROU     루마니아        ro      ron     루마니아어
18      LU    LUX     룩셈부르크      de      deu     독일어
19      LU    LUX     룩셈부르크      fr      fra     프랑스어
20      LY    LBY     리비아          ar      ara     아랍어
21      LT    LTU     리투아니아      lt      lit     리투아니아어
22      MK    MKD     마케도니아어    mk      mkd     마케도니아어
23      MX    MEX     멕시코          es      spa     스페인어
24      MA    MAR     모로코          ar      ara     아랍어
25      US    USA     미국            en      eng     영어
26      BH    BHR     바레인          ar      ara     아랍어
27      VE    VEN     베네수엘라      es      spa     스페인어
28      VN    VNM     베트남          vi      vie     베트남어
29      BE    BEL     벨기에          fr      fra     프랑스어
30      BE    BEL     벨기에          nl      nld     네덜란드어
31      BY    BLR     벨라루스        be      bel     벨로루시어
32      BA    BIH     보스니아 헤르체고비나   sr      srp     세르비아어
33      BO    BOL     볼리비아        es      spa     스페인어
34      BG    BGR     불가리아        bg      bul     불가리아어
35      BR    BRA     브라질  pt      por     포르투칼어
36      SA    SAU     사우디아라비아  ar      ara     아랍어
37      CS    SCG     세르비아 몬테네그로(유고슬라비아)       sr      srp     세르비아어
38      SD    SDN     수단    ar      ara     아랍어
39      SE    SWE     스웨덴  sv      swe     스웨덴어
40      CH    CHE     스위스  de      deu     독일어
41      CH    CHE     스위스  fr      fra     프랑스어
42      CH    CHE     스위스  it      ita     이탈리아어
43      ES    ESP     스페인  ca      cat     카탈로니아어
44      ES    ESP     스페인  es      spa     스페인어
45      SK    SVK     슬로바키아      sk      slk     슬로바키아어
46      SI    SVN     슬로베니아      sl      slv     슬로베니아어
47      SY    SYR     시리아  ar      ara     아랍어
48      AE    ARE     아랍에미리트    ar      ara     아랍어
49      AR    ARG     아르헨티나      es      spa     스페인어
50      IS    ISL     아이슬란드      is      isl     아이슬란드어
51      IE    IRL     아일랜드        en      eng     영어
52      AL    ALB     알바니아        sq      sqi     알바니아어
53      DZ    DZA     알제리  ar      ara     아랍어
54      EE    EST     에스토니아      et      est     에스토니아어
55      EC    ECU     에쿠아도르      es      spa     스페인어
56      SV    SLV     엘살바도르      es      spa     스페인어
57      GB    GBR     영국    en      eng     영어
58      YE    YEM     예멘    ar      ara     아랍어
59      OM    OMN     오만    ar      ara     아랍어
60      AU    AUS     오스트레일리아  en      eng     영어
61      AT    AUT     오스트리아      de      deu     독일어
62      HN    HND     온두라스        es      spa     스페인어
63      JO    JOR     요르단  ar      ara     아랍어
64      UY    URY     우루과이        es      spa     스페인어
65      UA    UKR     우크라이나      uk      ukr     우크라이나어
66      IQ    IRQ     이라크  ar      ara     아랍어
67      IL    ISR     이스라엘        iw      heb     히브리어
68      EG    EGY     이집트  ar      ara     아랍어
69      IT    ITA     이탈리아        it      ita     이탈리아어
70      IN    IND     인도    hi      hin     힌디어
71      IN    IND     인도    en      eng     영어
72      JP    JPN     일본    ja      jpn     일본어
73      CN    CHN     중국    zh      zho     중국어
74      CZ    CZE     체코    cs      ces     체코어
75      CL    CHL     칠레    es      spa     스페인어
76      QA    QAT     카타르  ar      ara     아랍어
77      CA    CAN     캐나다  en      eng     영어
78      CA    CAN     캐나다  fr      fra     프랑스어
79      CR    CRI     코스타리카      es      spa     스페인어
80      CO    COL     콜롬비아        es      spa     스페인어
81      KW    KWT     쿠웨이트        ar      ara     아랍어
82      HR    HRV     크로아티아      hr      hrv     크로아티아어
83      TH    THA     태국    th      tha     태국어
84      TH    THA     태국    th      tha     태국어
85      TR    TUR     터키    tr      tur     터키어
86      TN    TUN     튀니지  ar      ara     아랍어
87      PA    PAN     파나마  es      spa     스페인어
88      PY    PRY     파라과이        es      spa     스페인어
89      PE    PER     페루    es      spa     스페인어
90      PT    PRT     포르트칼        pt      por     포르투칼어
91      PL    POL     폴란드  pl      pol     폴란드어
92      PR    PRI     푸에르토리코    es      spa     스페인어
93      FR    FRA     프랑스  fr      fra     프랑스어
94      FI    FIN     핀란드  fi      fin     핀란드어
95      HU    HUN     헝가리  hu      hun     헝가리어
96      HK    HKG     홍콩    zh      zho     중국어


Java 에서 JSON 을 처리하기 위한 라이브러리는 많이 있지만 여러게 사용해본 결과  Jackson 라이브러리가 좋은것 같다

(실제로 High-performance JSON processor! 라고도 써있다.)

 

그냥 다운로드 받는 방법은 http://wiki.fasterxml.com/JacksonDownload 로 가서 최신 버전을 다운받아 해당 프로젝트의 라이브러리 폴더에 붙여 넣어서 사용한다.(core-asl 과 mapper-asl 를 받는다)

 

메이븐을 이용하는 경우는

 

1
2
3
4
5
<dependency>    
    <groupId>org.codehaus.jackson</groupId>    
    <artifactId>jackson-mapper-asl</artifactId>    
    <version>1.9.13</version>
</dependency>
cs

 

이렇게 하면 알아서 라이브러리에 추가 된다. 최신 버전 확인은 아래의 링크에서 확인한다.

최신 버전 확인 : http://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl

 

 

자바에서 사용 예제 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Map dummyData1 = new HashMap();
dummyData1.put("value1""11111");
dummyData1.put("value2""22222");
 
ObjectMapper om = new ObjectMapper();
 
try {   
    System.out.println(om.defaultPrettyPrintingWriter().writeValueAsString(dummyData1));
catch (JsonGenerationException e) {   
    e.printStackTrace();
catch (JsonMappingException e) {  
    e.printStackTrace();
catch (IOException e) {   
    e.printStackTrace();
}
cs

 

 

 

Jackson 라이브러리로 하는 짓들의 대부분은 ObjectMapper 라는 클래스의 인스턴스 생성을 한다음에 한다.

 

생성된 고것을 가지고 Json 문자열을 객체로 변환한다던가 객체를 JSON 문자열로 변환한다던가 한다. 뭐 다른 쓸만한 것도 많을 것이다. 

 

getter/setter 메소드 시리즈가 있는 도메인 오브젝트의 경우 writeValueAsString 메소드의 파라메터로 넣어서 사용 한다.

 

 

다음은 Spring Framework 에@ResponseBody를 이용한 messageConverter에 셋팅하는 방법을 알아 볼까한다

(다음에.. 언제인지 모름 -ㅅ-.)

 

 

오늘은 와이프님의 부탁으로 영문만 뽑아내는 자바 프로그램을 만들어 보았다 -_-

 

 

package expTest.com.exp.test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExpTestClass {

public static void main(String[] args) {
      Pattern nonValidPattern = Pattern.compile("[a-zA-Z]");

String str = "1234 @#$@#$@#abc123123ZZZZZZZZ1231312313";
Matcher matcher = nonValidPattern.matcher(str);
String result = ""; 
  
while (matcher.find()) {
    result += matcher.group(); 
}

 
           System.out.println(result);
        }

}

 

배워서 남주는것이 아니다 마누라 주는것이다 ㄷㄷㄷㄷ

+ Recent posts