JDK에는 Flight Recorder라는 훌륭한 프로파일러가 포함되어 있다. 진짜 괜찮은 프로파일러이다. 나는 JDK 8(101)을 쓰고 있다.


테스트를 위해서 CPU를 많으 쓰는  MyApp.java 를 생성한다. 



$ java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr MyApp

Started recording 1. The result will be written to:

..

디렉토리에 myrecoding.jfr 에 생성된다.


이제 설치된 java mission control을 실행한다.

$ jmc &


그리고, File - Open 을 클릭해서 myrecoding.jfr을 연다.



프로파일링 관련 다양한 정보를 보여준다.


Flight Recorder는 런타임에 JVM이 나타나는 이벤트를 기록할 수 있는 내부 JVM 후크(hook)를 사용하여 동작할 수 있도록 설계되었다. Flight Recorder가 캡처한 이벤트는 메모리 할당, 쓰레드 상태 변경, IO 동작, CPU 동작을 포함한다.



Flight Recorder는 비-상용 환경에서 무료로 사용할 수 있다. 상용 환경의 사용에 대해서 알려면, 오라클 라이선스-http://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/about.htm를 살펴본다.


문서를 보면, 다음과 같이 적혀 있다. 상용 환경에서 사용하려면 commercial license를 획득해야 한다.
Java Flight Recorder requires a commercial license for use in production. To learn more about commercial features and how to enable them please visit http://www.oracle.com/technetwork/java/javaseproducts/.


Flight Recorder는 JMC와 같은 Oracle Java SE Advanced & Suite Products 군에 속한다. 



자세한 내용은 아래를 참고한다. 


http://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/about.htm

Posted by '김용환'
,

외부 서버와 통신할 때, 결과를 enum으로 처리하는 경우가 종종 있다. 

String 리턴 값을 enum으로 변환하기 위해 자바 enum 타입으로 매핑하기 위해 작업하다가 NPE가 발생할 수 있다. 



java.lang.NullPointerException: Name is null

        at java.lang.Enum.valueOf(Enum.java:236)

        at ResultType.valueOf(ResultType.java:6)  // enum이 클래스로 변환하기 때문에 정확히 코드로 보이지 않는다. 






자바에서 enum을 정의하면, 자바 컴파일러에 의해 클래스로 변환된다. 

 (확인 방법은 http://varaneckas.com/jad/를 다운받고, http://knight76.tistory.com/entry/Jad-%EC%82%AC%EC%9A%A9%EB%B2%95로 사용한다)


public enum ResultType {

  SUCCESS("success"),

  ERROR("error");



  //..


// 내부 enum 필드를 map으로 저장한다.

private static Map<String, String> enumMap;


public static String getResult(String name) {

if (enumMap == null) {

    initializeMap();

}

    return enumMap.get(name);

}


private static Map<String, String> initializeMap() {

  enumMap = new HashMap<String, String>();

  for (ResultType resultType : resultType.values()) {

    enumMap.put(resultType.getName(), resultType.toString());

  }

  return enumMap;

 }

}



=>



  6 public final class ResultType extends Enum

  7 {

...


// 새로 추가되는 메소드

 50     public static ResultType valueOf(String s)

 51     {

 52         return (ResultType)Enum.valueOf(ResultType, s);

 53     }

 ..

// 나머지는 그대로

 



외부 서버와 통신한 후, 리턴 받은 값을 enum 으로 변환하기 위해 

String.valueOf()와 ResultType.getResult()를 사용하, 다시 이를 ResultType.valueOf()로 적용하면 원하는 값으로 바인딩 될 수 있다.


String result = restTemplate.exchange(...);

ResultType.valueOf(ResultType.getResult(String.valueOf(result)))



서로 약속한 값이 전달되면 문제는 없지만, 외부 통신 서버에서 결과 값을 이상하게 전달할 때, 에러가 발생한다. 


String result = restTemplate.exchange(...); // enum에서 정의되지 않은 "xxxxx"라는 스트링이 도착했다.

ResultType.valueOf(ResultType.getResult(String.valueOf(result)))


==> NPE : Name is null




외부 서버가 enum에 없는 값을 보내면서 ResultType.getResult()는 null을 리턴하고, enum이 클래스로 변환하면서 만들어진 ResultType.valueOf() 메소드는 내부적으로 Enum.valueOf()를 호출한다. 이 때 두 번째 매개변수가 null이 된다. Enum.valueOf() 메소드는 내부적으로 아래와 같이 되어 있고, name이 null이면 NPE 가 발생한다.


 


http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Enum.java#Enum.valueOf%28java.lang.Class%2Cjava.lang.String%29


207    public static <T extends Enum<T>> T More ...valueOf(Class<T> enumType,

208                                                String name) {

209        T result = enumType.enumConstantDirectory().get(name);

210        if (result != null)

211            return result;

212        if (name == null)

213            throw new NullPointerException("Name is null");

214        throw new IllegalArgumentException(

215            "No enum const " + enumType +"." + name);

216    }




따라서 외부 서버에서 들어온 값을 enum으로 변환할 때는 enum에서 선언한 필드가 아닌 값이 올 수 있고, 내부 코드에 따라 enum을 null로 바인딩될 수 있다는 것을 가정하고 코드를 짜는 것이 좋다.



String result = restTemplate.exchange(...);


if (ResultType.getResult(String.valueOf(result)) == null) {

 // 통신 에러 처리 

} else {

 ResultType type = ResultType.valueOf(ResultType.getResult(String.valueOf(result)))

 // 정상 처리

}






Posted by '김용환'
,



DND resolve 할 때, java 7에서는 gethostbyname_r/gethostbyaddr(_r)를 사용했지만, java 8 부터는 getaddrinfo/getaddrinfo로 바꿨다. 


https://bugs.openjdk.java.net/browse/JDK-7112670






레드햇에서는 이 이슈를 미리 발견해서 공지(https://access.redhat.com/solutions/22132)했다.


여러 서버로 바인된 도메인과 DNS RR로 설정된 경우에 대해, getaddrinfo를 호출하면 랜덤한 순서로 ip를 받지 않고,특정 ip만 리턴하여 특정 서버(또는 군)으로만 트래픽이 몰린다고 한다. 그리고, redhat 4~7까지 영향도가 있다고 적혀져 있다.



https://access.redhat.com/solutions/22132

  • DNS hostname lookups handled by the getaddrinfo(3) glibc function are no longer returned in "random" order when multiple A record answers are available in DNS.
  • Answers are sorted so some IP addresses are always returned first, thus "breaking" round-robin DNS.
  • This may also apply to entries in /etc/hosts if there are multiple entries for the same host.
  • Glibc resolver getaddrinfo(), rsh, and ssh do not try IP addresses in the order defined in /etc/hosts.





redhat 5에서는 발생했었고, 관련해서 /etc/gai.conf를 수정해서 해결되었다는 사례들이 인터넷에 있었다.


https://groups.google.com/forum/#!topic/consul-tool/AGgPjrrkw3g





하지만, centos 6.5(centos 7 포함)에서는 java 7에서 java 8 전환할 때, getaddrinfo에 따른 이슈는 발견되지 않았고, /etc/gai.conf를 수정할 일도 없었다.

Posted by '김용환'
,


Spring 3.2를 사용 중인 java 7 애플리케이션을 java 8 애플리케이션으로 전환하는 내용을 소개한다.


먼저 spring 3.2.4를 사용 중인 java7 애플리케이션을 바로 java 8로 컴파일하면, 바로 에러가 발생한다.


Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isn't supported yet:


; nested exception is java.lang.IllegalArgumentException
    at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:56)
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80)
    at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:102)
    at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:266)

    ... 64 more 






원인은 spring core의 asm이 java 8을 지원하지 않는다. (참고로 3.2.4 부터 spring-asm 모듈은 spring-core로 흡수되고, 사라졌다) 3.2.9부터 spring asm이 java 8 byte code을 인식할 수 있다. 하지만, fully가 아닌 best effort 수준이다. 완벽한 java 8은 spring 4부터 사용 가능하다.

(참고로 asm 모듈은 바이트 코드 분석 및 생성 역할을 한다.)


참고 : 

https://jira.spring.io/browse/SPR-11719

https://jira.spring.io/browse/SPR-11656


* 주의 사항은 지라에 잘 쓰여져 있다. 버전 이슈가 있어서 신중하게 쓸 필요가 있다. (현재는 3.2.17까지 릴리즈 된 상태이다)

With straightforward use of -target 1.8, there aren't many limitations to begin with. Spring 3.2.13 even includes the latest ASM 5.0.3 in the meantime, so it's fully up to date at that front. The only part worth noting is the AspectJ version: If you happen to be using AspectJ and in particular load-time weaving, there may be issues on Java 8 unless you upgrade to AspectJ 1.8.5; however, Spring 3.2.13 just supports AspectJ 1.7.4 - we haven't tested it with AspectJ 1.8.x at all.

Beyond that, Spring 3.2.x simply doesn't include any dedicated Java 8 support: i.e. no JSR-310 date-time, no parameter discovery via -parameters, etc. If you don't intend to rely on those features initially, that's by no means a showstopper. I would just strongly recommend an upgrade to Spring 4.x as soon as you intend to embrace Java 8 more fully. For the time being, using Spring 3.2.13 on Java 8 will get you quite far and is entirely safe for your purposes.

Note that we'll release 3.2.14 in May, as a minimal maintenance release, so that line is still actively supported for the time being. There's also a 3.2.15 release planned towards the end of this year, again designed as a very minimal maintenance release. Please be prepared that the 3.2.x line will fade out at that point, with its eventual end of life to be expected for mid/late 2016 and in all likelihood just one further maintenance release (3.2.16) to appear until then.




그 다음으로 수정한 것은 var argument 수정이었다.

java7에서는 적당히 쓸 수 있었는데. java8에서는 ambigous 한 문법이라고 문법 에러를 발생한다. 예를 들어, 


클래스에 test(String a, Object... args)와 test(String a, Message... args)는 java7에서 동작하지만, java8에서는 동작되지 않는다.






Posted by '김용환'
,


java 일반 객체에 Set으로 추가할 때, 특정 필드로 유일성을 보장하려면 hashCode()를 구현해야 한다. (참고로 Comparator(isEqual), compare() 메소드는 sorting 용이다.)


public class Up { private id; // setter, getter public int hashCode() {
return Long.hashCode(id);
} }



테스트 코드


Up up = new Up();
up.id = 1;
Up up2 = new Up();
up2.id = 1;

Set set = Sets.newHashSet();
set.add(up);
set.add(up2);

System.out.println(set);


up 객체에 hashcode() 를 상속/구현하지 않으면 set의 크기는 2가 된다.

Up[id=1..], Up[id=1..]



up 객체에 hashcode()를 추가하면, set의 크기는 1이다.

Up[id=1..]


* HashSet 구현 참조

add 할 때, 객체의 hash 코드를 참조한다.

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


Posted by '김용환'
,


자바에 primtive 타입에서 클래스 타입이 들어오면서 (int->Integer, boolean ->Boolean등)

좋아지는 부분도 있지만, 개발자들이 실수하는 코드도 있다.


특히 자바가 아닌 다른 언어에서는 자동으로 지원하지만, 자바는 되지 않는 것들이 바로 이런 경우일 것이다.





자바에서는 boolean 값에 null을 넣을 수 없다. (type mismatch)


boolean a = null;





하지만 아래 boolean과 Boolean을 동시에 사용하면, Boolean으로 변환(autoboxing)이 된다. 


다른 언어에서처럼 null에 대해서는 특별히 처리가 안되는 부분이 있다.




코드


@Test

public void test() {

boolean debug = isDebug();

System.out.println(debug);

}


public Boolean isDebug() {

return null;

}




결과

메소드의 리턴 결과에 Boolean 변수를  null을 리턴하면, NPE가 발생한다.

자바에 익숙치 않은 다른 언어 개발자는 null 을 사용하는 경향이 있는 것은

    if 문이 숫자 또는 null 에 대한 처리를 하기 때문인 듯하다. 



java language spec에 따르면, if문은 반드시 boolean이나 Boolean 이어야 한다.



https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.9

14.9. The if Statement

The if statement allows conditional execution of a statement or a conditional choice of two statements, executing one or the other but not both.

IfThenStatement:
IfThenElseStatement:
IfThenElseStatementNoShortIf:

The Expression must have type boolean or Boolean, or a compile-time error occurs.

Posted by '김용환'
,



Guava에서는 Set의 교집합(intersection)을 지원한다. 하지만, List쪽은 지원하지 않는다.


http://guava-libraries.googlecode.com/svn/tags/release04/javadoc/com/google/common/collect/Sets.html


 static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)



참고

http://knight76.tistory.com/entry/Guava-%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-Sets-api





여러 List 간 교집합을 간단히 구하려면, java.util.List의 retainAll() 메소드를 사용하면 교집합을 얻을 수 있다. 



@Test

public void retainTest() {

List<Integer> l1 = Lists.newArrayList(1, 2, 3);

List<Integer> l2 = Lists.newArrayList(3, 4, 5);


l1.retainAll(l2);

System.out.println(l1);


l2.retainAll(l1);

System.out.println(l2);


assertEquals(l1, l2);

}



결과

3




Posted by '김용환'
,





숫자(int)를 String으로 변경할 때, comma(,)를 같이 표현하고 싶을 때, 다양한 방법이 있지만,

java.x.text.NumberFormat을 활용하는 것도 좋은 방법이 될 수 있다.


import java.text.NumberFormat; @Test
public void a111222333() {
String numFormat = NumberFormat.getIntegerInstance().format(111222333);
System.out.println(numFormat);
Assert.equals("111,222,333", numFormat);
} //결과 111,222,333


Posted by '김용환'
,

java8 + centos7 이슈

java core 2016. 1. 5. 06:44

(정확히 왜 발생하는지 알 수는 없지만.) java8을 centos7기반 위에서 동작할 때, 이슈가 있다.


memcached와 web application server과의 connection pool은 문제가 없는데, 

redis와 DB의 connection pool만 "자원 고갈" 현상이 나타나고 있다.


즉, connection이 10개라면, socket이 하나씩 CLOSE_WAIT tcp status로 빠지고 더 이상 진척이 없다. 

결국 모든 socket이 CLOSE_WAIT가 되어 connection pool의 socket 자원이 없어지는 현상이 있다. 


centos 7 패치를 해도 문제가 일어나서, centos 6.5로 롤백했다.(참고로 6.5에서는 자원 고갈 현상이 발생하지 않았다.)

Posted by '김용환'
,



IntStream과 LongStream 에는 range와 rangeClosed 메소드를 지원한다. 이 둘로 범위를 지정할 수 있는데, Closed가 붙으면 끝 개수를 포함한다. 


다음은 결과 예제이다. 

import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class IntStreamTest {
public static void main(String[] args) {

IntStream
.range(1, 10)
.forEach(System.out::println);

System.out.println("");

IntStream
.rangeClosed(1, 10)
.forEach(System.out::println);

System.out.println("");

LongStream
.range(10000000000L, 10000000001L)
.forEach(System.out::println);

System.out.println("");

LongStream
.rangeClosed(10000000000L, 10000000001L)
.forEach(System.out::println);
}
}




결과 


1

2

3

4

5

6

7

8

9


1

2

3

4

5

6

7

8

9

10


10000000000


10000000000

10000000001


Process finished with exit code 0




쓰레드 5개를 한번에 생성해서 돌리고 싶다면, IntStream.rangeClosed()를 다음과 같이 이용해서 사용할 수 있다. 

import java.util.stream.IntStream;

public class IntStreamIntStreamTest {
public static void main(String[] args) {
IntStream.rangeClosed(1, 5).forEach(i->{
new Thread(()->{
System.out.println("Test!");
}).start();
});
}
}


Posted by '김용환'
,