Jul 2, 2018 - JVM heap memory 설정 변경 값

jmap -heap 9485

실제 서버에서 jmap 을 사용하여 heap 메모리 영역을 분석합니다.

ps -ef|grep tomcat

로 현재 프로세스를 확인하여 9485 process ID인 것을 확인했습니다.


Attaching to process ID 9485, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.112-b15

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 6438256640 (6140.0MB)
   NewSize                  = 1073020928 (1023.3125MB)
   MaxNewSize               = 1073020928 (1023.3125MB)
   OldSize                  = 5365235712 (5116.6875MB)
   NewRatio                 = 5
   SurvivorRatio            = 1024
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 1072037888 (1022.375MB)
   used     = 1071054848 (1021.4375MB)
   free     = 983040 (0.9375MB)
   99.90830174838% used
Eden Space:
   capacity = 1071054848 (1021.4375MB)
   used     = 1071054848 (1021.4375MB)
   free     = 0 (0.0MB)
   100.0% used
From Space:
   capacity = 983040 (0.9375MB)
   used     = 0 (0.0MB)
   free     = 983040 (0.9375MB)
   0.0% used
To Space:
   capacity = 983040 (0.9375MB)
   used     = 0 (0.0MB)
   free     = 983040 (0.9375MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 5365235712 (5116.6875MB)
   used     = 3631971624 (3463.7180557250977MB)
   free     = 1733264088 (1652.9694442749023MB)
   67.69453979210373% used
46179 interned Strings occupying 5111864 bytes.

옵션은 예전 JVM_OPT 제거 관련에서 제외하지 않은, 다른 서버를 기반으로 테스트 했습니다.

제가 관리하는 서버와는 다른 서버로,
실제 옵션은 다음과 같습니다. [JVM_OPT 제거 관련]글과 동일한 옵션입니다.

JAVA_OPTS="-verbosegc -server -Xms6144m -Xmx6144m -XX:NewRatio=5  -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=0 -XX:CMSInitiatingOccupancyFraction=75 -Djava.security.egd=file:/dev/urandom -Dscouter.config=/usr/local/tomcat7/conf/scouter.conf -Duser.timezone=GMT+09:00 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/tomcat7/logs/memory_err.log"

NewRatio=5 이므로, 실제 메시지가 5로 분배되며 그로 인해 1/(5+1)로 나누기 때문에,

OldSize = 5365235712 (5116.6875MB)
NewSize = 1073020928 (1023.3125MB)

전체 힙 사이즈의 크기 지정만큼 New 영역의 크기 지정 또한 중요하며,
XX:NewRatio 옵션을 이용해 전체 힙 크기 중 New 크기의 비율을 지정하거나 XX:NewSize 옵션을 사용해 원하는 크기만큼의 New 영역 크기를 지정하는 것이 좋다고 하는데, New 비율이 그다지 높지 않고, 아직까지 해당 서버는 static 서버의 비율이 조금 높습니다. (예전 서블릿 api 구조로 만들어진 서버.)

현재 우리 서버는 자바 1.8을 사용하고 있습니다.

에 Java8 부터 Permanent 영역에 대한 설정

-XX:PermSize=350m / 기동시 Perm사이즈
-XX:MaxPermSize=400m /최대사이즈

두 가지가 사라지고

-XX:MaxMetaspaceSize=
-XX:MetaspaceSize=

가 등장했다고 하는데요.

MetaspaceSize = 21807104 (20.796875MB)

Perm 영역에 대한 관리 문제(Out of Memory와 GC 성능)로 Perm+Native 영역을 통합해서,
Metaspace 영역으로 바뀌었다고 하는데, 결과적으로 PermGen 영역이 삭제되어 heap 영역에서 사용할 수 있는 메모리가 늘어났으며, PermGen 영역을 삭제하기 위해 존재했던 여러 복잡한 코드들이 삭제 되고 PermGen영역을 스캔 하기 위해 소모되었던 시간이 감소되어 GC 성능이 향상 되었다고 합니다.

물론, 대부분의 솔류션이 Spring 프레임웍으로 변경되면서 무분별한 static 사용이 줄어들었기 때문에, OOM 이 발생하는 거는 요근래 보기 힘들어졌지만, 해당 솔류션은 아직도 서블릿 API 이므로…

여튼, JAVA_OPTS을 적용하여, CMG 라든가 ratio 라든가 이런 부분이 적용이 되었습니다. 그리고 SurvivorRatio 나 MetaspaceSize 는 자동으로 적용이 되고요.

확인을 위해 제쪽에 적용된 셋팅(모든 설정은 지우고 default 처리)로 변경해봤습니다.

jmap -heap 3880

jmap -heap 3880
Attaching to process ID 3880, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.112-b15

using thread-local object allocation.
Parallel GC with 13 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6438256640 (6140.0MB)
   NewSize                  = 2145910784 (2046.5MB)
   MaxNewSize               = 2145910784 (2046.5MB)
   OldSize                  = 4292345856 (4093.5MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 2112356352 (2014.5MB)
   used     = 2112356352 (2014.5MB)
   free     = 0 (0.0MB)
   100.0% used
From Space:
   capacity = 16777216 (16.0MB)
   used     = 9126896 (8.704086303710938MB)
   free     = 7650320 (7.2959136962890625MB)
   54.40053939819336% used
To Space:
   capacity = 16777216 (16.0MB)
   used     = 0 (0.0MB)
   free     = 16777216 (16.0MB)
   0.0% used
PS Old Generation
   capacity = 4292345856 (4093.5MB)
   used     = 3228912792 (3079.331199645996MB)
   free     = 1063433064 (1014.1688003540039MB)
   75.22489800039077% used
43424 interned Strings occupying 4727664 bytes.

CMG 설정에 의해 강제로 Old Generation 이 75% 넘으면 강제 GC가 발생하도록 되어있었는데요.
75%가 넘어도 강제 GC를 발생시키지 않습니다.
Ratio 도 5 의 비율에서 2의 비율로 변경되었고, 그로인해, MaxNewSize 와 NewSize 비율도 자동으로 갱신되었습니다.

제가 관리하는 솔류션은, static을 거의 줄이고, new 객체로 전환시켜놓은것이 많다보니,
ratio option 을 안거는게 더 효율이 있었는데,
해당 솔류션은 사용한것과 사용하지 않은 것의 차이점이 크지 않았습니다. 제쪽은 작업 후 Full GC 와 GMC 가 사라졌는데, GMC가 사라지긴했지만, 그건 옵션을 제거해서 사라진 것이고 Full GC는 여전히 발생했습니다.

결국 큰 실익이 없다하여 예전 셋팅값으로 돌아갔습니다. 솔류션의 개발자들의 무분별한 static 변수들을 제거한 후에야,
재도전을 해볼 수 있을 거 같습니다.

Jun 29, 2018 - Exception in thread HttpServlet NoClassDefFoundError

"Exception in thread "main" java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet"

psvm 으로 main을 만들어 사용할 때,

Intellij 에서만 HttpServlet 을 못찾는 에러가 발생합니다. 이클립스라든가 IntelliJ 내부의 JUnit 을 사용했을 때는 정상적으로 동작합니다.

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>

보통, javax.servlet-api 는 서버에서는 tomcat 에 존재하므로, dependency의 scope를 provided로 적용해두고 있습니다.

그런데, main에서 호출할시에는 tomcat 과는 별개의 동작이므로, 외부에서 이 라이브러리가 제공되는 선언시, NoClassDefFoundError 가 발생하는 것입니다.

결국 IntelliJ 설정 중, provided 로 인해 발생한 문제로 확인이 되었습니다.

Include dependencies with “Provided” scope 를 체크하면, 정상적으로 동작합니다.

Jun 28, 2018 - Redis CLUSTER nodes.conf 파일 확인하며 장애 대응.

CLUSTER NODES

lod 현재 상황은 cluster nodes 라는 명령어를 통해 보일 수 있습니다.
CLUSTER NODES는 클러스터에 참여하고 있는 노드의 정보를 보는 명령으로,
접속한 서버가 가지고 있는 클러스터에 속한 다른 노들의 정보입니다.

clusternodeImg

  1. node-id : 노드를 유일하게 구분할 수 있는 ID며, 40개 문자로 구성되면 변경되지 않습니다.
  2. ip:port : 노드의 주소로 IP와 Port 입니다.
  3. flags : master, slave, fail?, fail, handshake, noaddr, noflags가 있고, 명령을 실행한 노드에 myself 라고 표시된다고 합니다.
  4. master : 슬레이브일 때 마스터 노드 ID가 표시되며, 마스터일 때는 “-“이 표시됩니다.
  5. ping-sent : myself가 다른 노드에 ping을 보낸 시각(Unix timestamp milliseconds)입니다.
    주의할 것은 7000번이 7001번에게 Ping을 보냈으면 ping-sent가 7000번에 기록되는 것이 아니고 7001번에 기록된다고 합니다.
    Ping-sent 시각은 Pong이 오면 지워지므로 이 시각은 아직 Pong 오지 않았을 아주 짧은 시간만 볼 수 있다고 합니다.
    따라서 대부분의 경우 0으로 나온며, Myself는 이 값이 항상 0입니다.
  6. pong-recv : Pong을 받은 마지막 시각(Unix timestamp milliseconds)을 의미합니다.
    7000번이 7001번으로부터 Pong을 받으면 7000번이 가지고 있는 7001번 데이터에 기록되며, Myself는 이 값이 항상 0입니다.
  7. config-epoch : The configuration epoch (or version) of the current node (or of the current master if the node is a slave). Each time there is a failover, a new, unique, monotonically increasing configuration epoch is created. If multiple nodes claim to serve the same hash slots, the one with higher configuration epoch wins.
  8. link-state : 클러스터 버스로 연결된 상태를 나타냅니다.
    connected 또는 disconnected 라고 합니다.
  9. slot : 마지막 항목은 할당된 슬롯 정보입니다.
    슬롯이 할당된 마스터 노드에 범위로 표시됩니다.
    슬레이브나 슬롯이 할당되지 않은 마스터는 표시되지 않습니다.

Redis CLUSTER nodes.conf 파일

서버에서 [CLUSTERDOWN The cluster is down] Error 가 발생하기 시작했습니다.

[ERROR][2018/06/28 16:04:31] c.m.d.r.RedisExecutor$RedisCommonPattern [86]
###### ERROR LOG ######
CLASS_NAME : Protocol.java / LINE : 115 / MESSAGE : redis.clients.jedis.exceptions.JedisClusterException: CLUSTERDOWN The cluster is down
CLASS_NAME : Protocol.java / LINE : 151 / MESSAGE : redis.clients.jedis.exceptions.JedisClusterException: CLUSTERDOWN The cluster is down
###### END ERROR LOG ######

SCOUTER 에서 사용하고 있는 ip 와는 다른 클러스터 ip 가 보이며, time out이 발생하는 현상이었습니다.
어플리케이션에서는 해당 IP를 호출하는 부분이 적혀 없었기에,

위의 서술한 cluster nodes 를 검색해보니, SCOUTER 에 찍혔던 ip 와 같은 클러스터 ip가 찍히는 것을 확인할 수 있었습니다.

원인은 별도의 검증계 서버를 구축하기 위해 레디스를 구축하던 과정에서, 뭔가 grouping 이 가능한 동일 id를 설정하면서, 해당 ip 까지 동일한 클러스터로 판단하여, 클러스터링된것으로 보이며,

급한 이슈 해결을 해야하므로, 레디스를 다시 밀어버리고, 재설치하는 방법으로 해결되었습니다. (이게 해결일 수는 없습니다.)