2015년 3월 18일 수요일

JAVA8 Permanent 영역은 어디로 가는가

JDK 8이 발표 된지도 좀 되었다.

얼마전에 그 여파로 GC 내용도 좀 정리해봤는데
(http://yckwon2nd.blogspot.kr/2014/04/garbage-collection.html)

포스팅 말미에 Java8 부터 Permanent 영역에 대한 설정

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

두 가지가 사라지고

-XX:MaxMetaspaceSize=
-XX:MetaspaceSize=

가 등장했다.

java 개발자들이 바보가 아니고서야 쓸데 없이 이름을 왜 바꾸었을까 싶었는데

보다 보니 이래저래 Perm 에 대한 전략이 수정된것 같아서 정리해 본다.



java7 까지의 permanent(영속적, 이하 perm) 영역에는  다음과 같은 정보들이 저장되었다.

1. Class 의 Meta정보 (pkg path 정보라고 보면 됨, text 정보)
2. Method의  Meta 정보
3. Static Object
4. 상수화된 String Object
5. Class와 관련된 배열 객체 Meta 정보
6. JVM 내부적인 객체들과 최적화컴파일러(JIT)의 최적화 정보

-Methods of a class (including the bytecodes)
-Names of the classes (in the form of an object that points to a string also in the permanent generation)
-Constant pool information (data read from the class file, see chapter 4 of the JVM specification for all the details).
-Object arrays and type arrays associated with a class (e.g., an object array containing references to methods).
-Internal objects created by the JVM (java/lang/Object or java/lang/exception for instance)
-Information used for optimization by the compilers (JITs)

해깔려들 하시는게 static이 어떻게 저장되느냐 인데
변수의 경우 그냥 값 자체가 perm에 들어갑니다. 만약 Object 라면 Object 자체가 저장되지요
static int i = 1; //the value 1 is stored in the permgen section

요즘의 거의 이런일은 없지만 개발자가 별생각없이 Collection Object를 Static 으로 선언하고 값을 계속 추가하다보면 Perm 이 가득차서 OOM이 발생하기도 했지요...


Class의 경우 JVM기동시 사용되는 모든 Class와 Method Meta 정보가 Perm에 저장되고
Code 상에 아래와 같이 static으로 정의되면 해당 메타 정보도 Perm에 들어가게 되지요
static Object o = new SomeObject(); //the reference(pointer/memory address) is stored in the permgen section, the object itself is not.


결국 Perm에 저장되는 위 여섯가지 중에서 Perm 부족을 일으키는 상대는
1. Static Object의 잘못된 사용 (개발자가 원인)
2. Class, Method Meta data의 증가   (hotDeploy가 원인)

두 가지가 가장 큰 문제였고 Perm 구조를 가지는 Sun JVM  계열 (Open JDK, HP JDK 등)에서는 Perm 누수는 운영에 큰 불편함을 감수하도록 했습니다.

그래서 결국 결정은 이겁니다.

"Permanent 영역을 없에 버리자!!!"

특히나  Oracle의 경우

BEA 라는 회사(Weblogic을 개발했고 자체 JVM 으로 JRockit 을 개발한)를 인수해서 JRockit 을 가지고 있다가 Sun의 JAVA 팀을 먹은 이후로

JRockit의 지향점 (대용량 Heap 지원을 위한 구조, Perm 없음, Survive 영역없음, NEW/OLD 만 있음)을 Java 8에 적용하게 되었고 Perm 영역이라는 개념을 제거해 버립니다.
G1 알고리즘도 지향점은 동일 합니다.

그럼....

거기 저장되던 것들은 어떻게 되는데????
(아래에서 Native 는 Metaspace를 의미함)

1. Class 의 Meta정보 (pkg path 정보라고 보면 됨, text 정보)  --> Native 영역으로 이동
2. Method의  Meta 정보--> Native 영역으로 이동
3. Static Object --> Heap 영역으로 이동
4. 상수화된 String Obejct  --> Heap 영역으로 이동
5. 클레스와 관련된 배열 객체 Meta 정보  --> ???? (아티클 봐서는 모르겠는데 Native로 간듯)
6. JVM 내부적인 객체들과 최적화컴파일러(JIT)의 최적화 정보 --> ???   (아티클 봐서는 모르겠는데 Native로 간듯)


정리하면 java7  까지는
new / survive / old / perm /  native  로 구분했다면
java 8에서는
new / survive / old / metaspace 로 아키텍쳐가 변경되었고

기존의 perm에 저장되어 문제를 유발하던
static obect는 heap으로 옮겨서 GC 대상이 최대한 될 수 있도록 하고 (Final 로 해놓으면 나두 모름)

기타 정말 수정이 될 일 없어보는 정보는
Native(Metaspace) 로 몰아넣고 사이즈는 자동적으로 조정되도록 개선 되었다고 정리하면 되겠습니다.

.....

여기서 개인적으로 두려운것은...

사실 최근에는 Framework의 대중적인 사용 및 개발자 수준향상(?)으로 미친 Static 사용문제는 거의 발생하지 않고 있고
Perm의 가장 큰 문제로 JSP를 지속적으로 HotDeploy 하는 회사에서 발생하는 Perm 누수문제 인데...
(Production 에서 Class 까지 Hotdeploy 하지는 않겠찌?????)

이게... Perm을 Max를 정해놓고 할때는 Perm이 꽉 차면 OOM이 발생
“java.lang.OutOfMemoryError: PermGen space” error 

하고 장애로 확인되면 설명을 해주고 대응방안으로

1. JSP Hot Deploy 하지 마라  (서비스 요건상 해야 되면 어쩔 수 없다면...)

2. Perm을 적절히 늘리자
어쩔 수 없이 JSP hostdeploy를 해야 한다면 늘어나는 추이를 분석해서 최소한 3일 이상 버티도록 = (금요일퇴근, 토,일 은 버텨야 하지 않겠는가) 설정을 늘려준다

3. 정기적으로 재구동
요건상 누수를 안고 가야 하므로 배포 주기나 일정기간 마다 통제된 환경에서 재구동 해주는 운영상의 방법 사용

을 설명해준다.
(Perm 을 GC할 수 있도록 해달라고 땡깡 부리는 사람들이 있는데....제기랄....난 모른다. Context 개념도 없는 사람에게 Perm의 Meta 정보가 쌓이는 메커니즘을 설명 할 방법이 없다)


그런데 이제는 이 문제를 Native 영역으로 옮겨버린단다...
거기다 -XX:MaxMetaspaceSize 설정을 하지 않으면 JVM이 알아서 사이즈를 조정한다고 하는데....

알다시피 JVM 장애시 Native Memory 영역의 누수는 분석이 불가능하다 (dump를 생성해도 Native 영역 정보는 생성 되지 않는다)

이렇게 되면 새로운 어려움이 생기는데...

Core  파일을 생성해서 gdb로 누수 현상에 대해서 분석해야 하는...????

난 모르겠다...  이건 내 수준을 벗어나는 부분이라 어찌될지...
(어짜피 Perm도 분석대상이 아니였으니 그다지 문제가 되지 않을 지도...)


여하간  아래 4개의 값을 어떻게 잡고 갈지가 아직 오리 무중이다.

일단 기본적으로 Perm에 적용하던 기본값 (min 256m, max 512m)으로 잡고 GC정보를 보면서 경험치를 늘려 가는 방법뿐일 듯

  • -XX:MetaspaceSize=<NNN> where <NNN> is the initial amount of space(the initial high-water-mark) allocated for class metadata (in bytes) that may induce a garbage collection to unload classes. The amount is approximate. After the high-water-mark is first reached, the next high-water-mark is managed by the garbage collector
  • -XX:MaxMetaspaceSize=<NNN> where <NNN> is the maximum amount of space to be allocated for class metadata (in bytes). This flag can be used to limit the amount of space allocated for class metadata. This value is approximate. By default there is no limit set.
  • -XX:MinMetaspaceFreeRatio=<NNN> where <NNN> is the minimum percentage of class metadata capacity free after a GC to avoid an increase in the amount of space (high-water-mark) allocated for class metadata that will induce a garbage collection.
  • -XX:MaxMetaspaceFreeRatio=<NNN> where <NNN> is the maximum percentage of class metadata capacity free after a GC to avoid a reduction in the amount of space (high-water-mark) allocated for class metadata that will induce a garbage collection.


참고
https://blogs.oracle.com/poonam/entry/about_g1_garbage_collector_permanent
http://java.dzone.com/articles/java-8-permgen-metaspace
http://www.infoq.com/articles/Java-PERMGEN-Removed




common DBCP 사용시 Thread lock 문제 - CASE2

또다른 CASE는 아래 링크 클릭하면 볼 수 있다. (두개 다 동일한 문제라고 보면 됨)
http://yckwon2nd.blogspot.kr/2014/04/common-dbcp-thread-lock.html

1. 증상
WAS의 Thread가 모두 소진되어 서비스가 되지 않거나 그에 의한 여파(처리가 안되면 모든 객체가 대기 되므로)로 Full GC가 발생하여 서비스 지연 또는 안되는 현상

2. 원인
Apache org에서 제공되는 Common DBCP module은 Thread 안정성을 위해서 Single Thread로 동작합니다.
간략하게 설명하면
Connection 하나를 APP가 받아서 사용할 때 Pool 자체에 대한 접근을 차단 하게되며
APP가 느릴 경우 다른 Connection을 요구하는 모든 APP가 정지 되는현상 (Thrad Lock wait)이 발생합니다.
보내준 내용을 보면 1Thread에 의해 670개의 요청이 대기 상태가 됨을 알 수 있음
<0x00000000c00c6070> (a org.apache.commons.dbcp.AbandonedObjectPool):    0 Thread(s) sleeping, 670 Thread(s) waiting, 1 Thread(s) locking

3. 해결
Tomcat의 경우 Common DBCP가 아닌 tomcat에 포함된 Tomcat DBCP를 사용하거나
Jboss, Weblogic 등의 엔터프라이즈 WAS를 사용할 경우 해당 제품에서 제공되는 DB Pool을 사용하여 해결 가능함

{
PC에서 똑딱거리면서 하겠다고 DBCP로 개발하는건 좋은데 
제발 Production환경으로 가면서 WAS Pool안쓰고 버티는 똥배짱좀 부리지 말자 
Framework 쓰자나... conf 파일 하나 바꿔서 JNDI  Lookup만 맞춰주면 되는데
대체  왜그래....
}

4. 설명
아래 thread를 보면 APP가 Altibase DB로 쿼리 수행 후 시간이 지연되어 대기 중인 상태임
이때 볼드 처리된 라인을 보면 DB Connection Pool에 대하여 Lock을 획득했는데 처리가 늦어지면서 해당 객체를 돌려주지 못함
(common DBCP는 single thread로 동작하기 때문에 이렇게 됨)
일반적으로 Common DBCP를 사용하면
- locked <0x00000007957d27d8> (a org.apache.commons.dbcp.BasicDataSource)
와 같이 BasciDatasource가 lock되나 아래에 보면
- locked <0x00000000c00c6070> (a org.apache.commons.dbcp.AbandonedObjectPool)
으로 이름이 다름

이유는 DBCP에서 Connection 의 비정상 회수 시 일정시간 후에 Connection을 폐기하도록 하는 Abandone설정을 활성화하면
다른 Class가 사용되므로 Thread 정보에서 다르게 보일 수 있으나 원인과 증상은 동일함

Thread-1184" daemon prio=10 tid=0x00007fa2b8010000 nid=0x2112 runnable [0x00007fa24d2d0000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
        at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
        at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:218)
        at sun.nio.ch.IOUtil.read(IOUtil.java:186)
        at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:359)
        - locked <0x00000000c0659998> (a java.lang.Object)
        at Altibase.jdbc.driver.cmnTCP.recv(cmnTCP.java:276)
        at Altibase.jdbc.driver.cmp.flush(cmp.java:294)
        at Altibase.jdbc.driver.cmp.writeDirectExecuteAndFetchReq(cmp.java:1227)
        - locked <0x00000000c0619b78> (a Altibase.jdbc.driver.cmp)
        at Altibase.jdbc.driver.ABConnection.writeDirectExecuteFetchReq(ABConnection.java:624)
        at Altibase.jdbc.driver.ABStatement.executeQuery(ABStatement.java:321)
        - locked <0x00000000ce0fe9b8> (a Altibase.jdbc.driver.ABStatement)
        at org.apache.commons.dbcp.DelegatingStatement.executeQuery(DelegatingStatement.java:208)
        at org.apache.commons.dbcp.DelegatingStatement.executeQuery(DelegatingStatement.java:208)
        at org.apache.commons.dbcp.PoolableConnectionFactory.validateConnection(PoolableConnectionFactory.java:332)
        at org.apache.commons.dbcp.PoolableConnectionFactory.validateObject(PoolableConnectionFactory.java:312)
        at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:855)
        - locked <0x00000000c00c6070> (a org.apache.commons.dbcp.AbandonedObjectPool)
        at
org.apache.commons.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:84)
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:96)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:880)
        at devonframework.persistent.connection.LJndiDataSource.if(Unknown Source)
...이하 생략


PS. 피해자들 과 잘못된 접근시 삽질 Sequence

{
아래같은 Thread가  670개 대기중 --> 
제니퍼에서 보면 Active Eq에 670개 쌓여서 뻘겋게 보임 --> 
670개 요청이 메모리를 잡아먹고 버티니 WAS Heap이 남아도는 사이트가 아니라면 Full GC가 지속적으로 발생 --> 
Full GC 정보 보고 Heap memory 부족한거 아니냐고 개드립시작함....  --> 
(기타 : WAS Thread 가 모자라다, DB Connection이 부족하다 등의 개드립도 있음)
메모리 덤프떠서 메모리 분석 한다 -->
메모리에 보면 졸라 다양한 APP들이 이상한 객체를 만들고 있고 DB Connection 관련 Object가 엄청 많다(메모리 내용으로는 뭐가 문제인지 알 수 가 없다.)  -->
DB Connection 또는 Pool이 버그라고 개드립 -->
제품 버그 찾는다고 구글링 십년....  -->
잘못하면 삼천포에 빠져죽는다...

약은 약사에게 병은 의사에게....
}

"Thread-1730" daemon prio=10 tid=0x00007fa2b0103800 nid=0xc29 waiting for monitor entry [0x00007fa23c1c1000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.apache.commons.pool.impl.GenericObjectPool.getNumIdle(GenericObjectPool.java:911)
        - waiting to lock <0x00000000c00c6070> (a org.apache.commons.dbcp.AbandonedObjectPool)
        at org.apache.commons.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:78)
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:96)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:880)
        at devonframework.persistent.connection.LJndiDataSource.if(Unknown Source)
        at devonframework.persistent.connection.LDataSourcePool.getJNDIConnection(Unknown Source)
        at devonframework.persistent.connection.LDataSourcePool.getConnection(Unknown Source)
        at devonframework.persistent.dao.LConnectionManager.getConnection(Unknown Source)
        at devonframework.business.transaction.LConnectionMapper.getConnection(Unknown Source)
        at devonframework.persistent.autodao.LAutoDao.getConnection(Unknown Source)
        at devonframework.persistent.autodao.LCommonDao.executeQueryForSingle(Unknown Source)
        at devon.batch.core.persistence.BatchDao$JobInfo.retrieveSingleJobGroup(Unknown Source)
        at devon.batch.core.persistence.BatchDao$JobInfo.retrieveSingleJobGroup(Unknown Source)
        at devon.batch.core.persistence.BatchDao$JobInfo.retrieveJobGroupDescriptor(Unknown Source)
        at devon.batch.scheduler.trigger.JobGroupExecOrder.beforeSend(JobGroupExecOrder.java:106)
        at devon.batch.scheduler.trigger.AbstractTransferOrder.send(AbstractTransferOrder.java:93)
        at devon.batch.scheduler.manager.SchedulerManager.sendJobGroupExecOrder(SchedulerManager.java:179)
        at devon.batch.scheduler.manager.LSchedulerItem.doIt(LSchedulerItem.java:70)
        at devonframework.service.scheduler.job.LJob$1.run(Unknown Source)



2015년 3월 1일 일요일

유리창 밖의 세상...


세상을

창 안에 앉아 느긋하게 바라본다는것은

여유일까 방관일까..



연일 터지는 사고와 사건

대통령의 정통성, 대의명분, 국정원의 비 윤리, 대기업의 횡포, 미친 갑들의 발악, 이상한 총리, 노무현음해, 전단지살포, 서울의 시위...

프레임안에 잡히기 싫어서 밖에서 바라본다는 시각은

결국 나를 안으로 가두고 있는것이 아닐까

누군가는 게으르다, 해야할 의무를 방임하고 있다, 책임감이 없다고 하겠고

사실 맞는 말이라 반박도 못한다.

열심히 달려가는 사람들을 숨어서 빼꼼~ 한 눈으로 바라보는 나는

비겁함을 변명하고

게으름을 속이며

결국 나 자신을 가두고 살아가고 있다.

이 안락한 유리창의 문을 열면 쏟아져들어올 비바람을 감당하고 싶지 않다.

내가 그 상황을 감당하기 싫고

나에게 의존하는 사람들에게 까지 그 짐을 씌우기 싫다는 변명으로

이렇게 살아가고 있다.


세상이 상식적으로 돌아가는대 꼭 사람의  피가 필요한가

"자유라는 말에는 피 냄새가 난다" 라고 누군가 했었더랬는데

비릿한 내음이 싸늘한 봄 바람을 타고 흐른다