java에서 jvm으로 넘어오면 파라미터를 체크하려면 좀 귀찮은 작업을 해야 하는데. Apache common cli를 쓰면 좀 편리하다.  properties 체크도 할 때 하려면 귀찮아서 추후를 위해서 만들어본다.


Test.java

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;


public class Test {
 public static final String DEFAULT_CONFIG = "a.prop";
 
 public static void main(String[] args) throws Exception {
  Map<String, String> propMap = readProperties();
  Map<String, String> argMap = readOption(args);
  if (argMap == null) {
   return;
  }
  
  //...
  System.out.println("property..");
  for (Map.Entry<String,String> entry : propMap.entrySet()) {
   System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
  }
  
  System.out.println("argument..");
  for (Map.Entry<String,String> entry : argMap.entrySet()) {
   System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue());
  }
 }

 private static Map<String, String> readOption(String[] args) throws ParseException {
  Options options = new Options();
  options.addOption("m", "mode", true, "mode data");
  options.addOption("n", "num", true, "number data");

  CommandLineParser parser = new PosixParser();
  CommandLine cmd = parser.parse(options, args);

  if (!cmd.hasOption('n') || !cmd.hasOption('m')) {
   HelpFormatter formatter = new HelpFormatter();
   formatter.printHelp("Help ....", options);
   return null;
  }
  
  Map<String, String> map = new HashMap<String, String>();
  String number = cmd.getOptionValue('n');
  String mode = cmd.getOptionValue('m');
  map.put("number", number);
  map.put("mode", mode);
  return map;
 }

 private static Map<String, String> readProperties() throws IOException {
  InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_CONFIG);
  if (in == null) {
   throw new IOException("Not found config file " + DEFAULT_CONFIG);
  }

  Properties properties = new Properties();
  properties.load(in);
  
  Map<String, String> map = new HashMap<String, String>();
  for (Map.Entry<Object,Object> entry : properties.entrySet()) {
   map.put((String) entry.getKey(), (String)entry.getValue());
  }
  return map;
 }
}




a.prop


a=1
b=2


그냥 실행시에는 에러가 발생


usage: Help ....
 -m,--mode <arg>   mode data
 -n,--num <arg>    number data


파라미터를 넘기면(-n 5 -m run) 다음과 같은 결과 나옴

property..
key : b, value : 2
key : a, value : 1
argument..
key : number, value : 5
key : mode, value : run




pom.xml의 추가할 depdendency 추가.



  <dependency>
   <groupId>commons-cli</groupId>
   <artifactId>commons-cli</artifactId>
   <version>1.1</version>
  </dependency>
Posted by '김용환'
,

 

tomcat 6 에서 동작하던 웹 어플리케이션을 tomcat 7 (7.0.25) 으로 올려보았다.  tomcat 7이 servlet 3.0의 comet을 지원하는 것외에 특별히 고쳤을까 싶었는데..

tomcat6의 catalina.sh를 그대로 사용할 때와 tag library쪽에 이슈가 있었다.

 

## catalina.sh start 되게

설정 파일 conf/server.xml과 bin/catalina.sh 는 tomcat 6에 있는 것으로 사용하려고 하다가 다음과 같은 에러를 만났다. (역시 tomcat 7의 catalina.sh을 사용하는 것이 맞을 듯.)

java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory

 

tomcat7의 catalina.sh 에 classpath에 bin/tomcat-juli.jar 부분이 변경된 것 같다.

# Add tomcat-juli.jar to classpath
# tomcat-juli.jar can be over-ridden per instance
if [ -r "$CATALINA_BASE/bin/tomcat-juli.jar" ] ; then
  CLASSPATH=$CLASSPATH:$CATALINA_BASE/bin/tomcat-juli.jar
else
  CLASSPATH=$CLASSPATH:$CATALINA_HOME/bin/tomcat-juli.jar
fi

 

또한 입맛에 좀 맞게 catalina.sh를 변경해야 한다.  (CATALINA_JAVAOPTS, $CATALINA_LOGDIR, CATALINA_LOGFILE, CATALINA_HOME)

conf/server.xml은 그대로 수정해서 사용하니 잘 동작된다.

 

## catalina.sh  stop되게

stop 시에 대한 설정을 일부 수정해야 한다.

stop을 하면, sleep 5초를 한다. (디폴트) kill 하고, 안죽으면 force하게 죽는 작업이 되어 있다.

이 부분을 입맛에 맞게 변경

 

## catalina.sh configtest

아파치의 문법 체크(httpd -t )처럼 설정 문법을 테스트하는게 생겼다.

 

소스는 다음과 같다.

elif [ "$1" = "configtest" ] ; then
   
    eval \"$_RUNJAVA\" $JAVA_OPTS \
      -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \
      -Dcatalina.base=\"$CATALINA_BASE\" \
      -Dcatalina.home=\"$CATALINA_HOME\" \
      -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \
      org.apache.catalina.startup.Bootstrap configtest
    result=$?
    if [ $result -ne 0 ]; then
        echo "Configuration error detected!"
    fi   
    exit $result
fi

 

 

## Tomcat 이슈

tomcat6에서 잘 동작하는 소스를 tomcat 7에서 돌릴 때는 어떠할까. 태그 라이브러리쪽에 이슈가 있었다.

org.apache.jasper.JasperException: The absolute uri: http://taglib.google.com/hiu cannot be resolved in either web.xml or the jar files deployed with this application

at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:56)
at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:410)
at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:117)
at org.apache.jasper.compiler.TagLibraryInfoImpl.generateTLDLocation(TagLibraryInfoImpl.java:311)
at org.apache.jasper.compiler.TagLibraryInfoImpl.<init>(TagLibraryInfoImpl.java:152)
at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:410)
at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:475)
at org.apache.jasper.compiler.Parser.parseElements(Parser.java:1427)
at org.apache.jasper.compiler.Parser.parse(Parser.java:138)
at org.apache.jasper.compiler.ParserController.doParse(ParserController.java:242)
at org.apache.jasper.compiler.ParserController.parse(ParserController.java:102)
at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:198)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:373)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:353)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:340)
at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:646)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:357)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)

 

tomcat 6에서는 기존에 tag library 파일들을 META-INF 디렉토리안에 넣으면 자동으로 인식하는 기능이 있었는데, 이 부분이 tomcat 7부터는 web.xml에 명시적으로 관련 정보를 넣는 구조로 바뀌었다.

 

즉 기존의 파일은 다음과 같이 사용했다.


src/main/java/META-INF/taglib/hiu-taglib.tld

<?xml version="1.0" encoding="UTF-8"?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <description>HIU Custom Tags</description>
    <tlib-version>1.2</tlib-version>
    <short-name>hiu</short-name>
    <uri>http://taglib.google.com/hiu</uri>


<tag>

….

</tag>

…..

 

web.xml에 taglibarary에 대한 url와 location에 대해서 명확히 지정해야 한다.

(tld 파일을 META-INF가 아닌 WEB-INF 로 이동해야 잘 인식해서 WEB-INF 로 이동하니 잘된다.
/META-INF/taglib.tld 파일로 수정해도 인식 못한다. )

* web.xml에 추가할 내용

<jsp-config>
    <taglib>
        <taglib-uri>http://taglib.google.com/hiu</taglib-uri>
        <taglib-location>/WEB-INF/hiu-taglib.tld</taglib-location>
    </taglib>
</jsp-config>

WEB-INF 디렉토리 밑에 tag lib를 두면 자동으로 인식하기 때문에 꼭 저렇게 사용하지는 않아도 된다.

정확한 지식을 위해서 jsp 2.2 스펙을 참조한다.

 

 

* Java Server Page 2.2 specification (tomcat 7이 적용, jsp 2.2) 
http://jcp.org/aboutJava/communityprocess/mrel/jsr245/index.html

스펙을 참조하니. 이해가 되었다. 역시 스펙의 힘이란…

 

예제가 설명이 되어 있다.

 

순서에 대한 정보도 있다.

자세한 내용은 아래를 참조하면 된다.

JSP.7.3.2 TLD resource path
JSP.7.3.3 Taglib Map in web.xml
JSP.7.3.4 Implicit Map Entries from TLDs
JSP.7.3.5 Implicit Map Entries from the Container
JSP.7.3.6 Determining the TLD Resource Path

Posted by '김용환'
,

 

아래 블로그에 작성한 JAXB HttpClient 코드를 Spring OXM으로 수정해서 돌려보았다. 비교해볼만함

http://knight76.tistory.com/entry/JAXB-Http-Client-Example샘플

 

 

특정 URL을 호출하면 사원 정보가 나온다고 가정한다.

 

<ROOT>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

</ROOT>

 

# ROOT.java

package spring;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="ROOT")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
   
    @XmlElement(name="EMPLOYEE")
    private List<Employee> employees;

    public Root() {
        employees = new ArrayList<Employee>();
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

}

 

# Employee.java

package test;

public class Employee {

private String id;
private String nm;

….

… // set, get

}

 

# XEmployee.java

 

public class XEmployee {

    private static List<String> employeeList;
    public static List<String> getEmployeeList() {
        return employeeList;
    }
    static {
        employeeList = new ArrayList<String>();

        employeeList.add("aaa");
         employeeList.add("bbb");
         employeeList.add("ccc");

    }
}

여기까지는 바뀌는 것이 없다.  Spring 연동 코드가 바뀌었다. 연동코드는 형광펜으로 칠해놨다.

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/oxm
           http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

    <oxm:jaxb2-marshaller id="jaxb2Marshaller">
        <oxm:class-to-be-bound name="spring.Root" />
    </oxm:jaxb2-marshaller>
   
    <!-- oxm:jaxb2-marshaller 에서 사용했던 것과 동일한 효과(예전 방식) 
    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>spring.Root</value>
            </list>
        </property>
    </bean>
    -->

    <bean id="application" class="spring.CheckEmp">
        <property name="marshaller" ref="jaxb2Marshaller" />
        <property name="unmarshaller" ref="jaxb2Marshaller" />
    </bean>

</beans>

 

main 메서드

package spring;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.transform.stream.StreamSource;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class CheckEmp {
    @Resource(name = "jaxb2Marshaller")
    Unmarshaller unmarshaller;

    @Resource(name = "jaxb2Marshaller")
    Marshaller marshaller;
   
    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }
   
    public static final boolean isSaved = false;
    public static final String URL = "http://aaaaaaaaa";
    public static JAXBContext jaxbContext;
    static {
        try {
            jaxbContext = JAXBContext
                    .newInstance(new Class[] { test.Root.class });
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

       public static void main(String[] args) throws Exception {
        ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CheckEmp application = (CheckEmp) appContext.getBean("application");
        application.start();
    }

    private void start() throws Exception {
        BufferedReader reader = connectURL();
        if (isSaved) {
            saveFile(reader);
        }

        Root root = getRoot(reader);

         for (String retiredEmpID : XEmployee.getEmployeeList()) {
            boolean found = false;
            for (Employee emp : root.getEmployees()) {
                if (emp.getID().trim().equalsIgnoreCase(retiredEmpID)) {
                      found = true;
                }
            }
            if (found == false) {
                System.out.println(retiredEmpID);
            }
        }
    }

    private Root getRoot(BufferedReader reader) throws Exception {
        StreamSource source = new StreamSource(reader);
        Root root = (Root) unmarshaller.unmarshal(source);
        return root;
    }

    private BufferedReader connectURL() throws IOException,
            ClientProtocolException, UnsupportedEncodingException {
        HttpGet httpget = new HttpGet(URL);
        HttpClient httpclient = new DefaultHttpClient();
        httpclient.getParams().setParameter("http.socket.timeout", 999000);

        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new RuntimeException("url error");
        }
        InputStream instream = entity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                instream, "utf-8"));
        return reader;
    }

    private static void saveFile(BufferedReader reader) throws IOException {
        Writer fstream = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream("aaa.txt"), "utf-8"));
        BufferedWriter out = new BufferedWriter(fstream);
        char[] a = new char[1024];
        while (reader.read(a) > 0) {
            out.write(a);
        }
        out.flush();
        System.out.println("File created successfully.");
    }

}

 

삽질 방지를 위해서 spring debug log가 출력되게 한다.

log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Threshold" value="DEBUG"/>
        <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/>
        </layout>
    </appender>
    <appender name="general" class="org.apache.log4j.ConsoleAppender">
       <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/>
       </layout>
    </appender>

    <logger name="com" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="general"/>
    </logger>

    <logger name="org.springframework" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="general"/>
    </logger>

    <root>
        <level value="WARN"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test1123</groupId>
    <artifactId>teswt1</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <spring-version>3.1.0.RELEASE</spring-version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-xjc</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jsr250-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>

</project>

 

 

# 참조한 내용

Spring OXM 에 대한 자료 (Marshalling XML using O/X Mappers)

http://static.springsource.org/spring-ws/site/reference/html/oxm.html

 

 

# 기타

 

@Resource  annotation은 javax.annotation으로 플랫폼 독립적이며, jsr250에서 DI 의 리더들이 모여서 결정했다. jsr250-api.jar 를 반드시 pom.xml에 넣어야 한다.

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>

name속성에 자동으로 연결될 빈객체의 이름을 입력한다
@Resource(name="marshaller")
Marshaller marshaller;




# CheckEmp.java 코드를 Spring JUnit으로 만들기

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml"})
public class CheckEmp {

// 코드

 @Test
 public void testMarshaller() throws Exception {

 ...
}
}

Posted by '김용환'
,

 

apache commons의 http client와 jaxb로 어떻게 테스트할지에 대해서 작성한 글이다.

특정 URL을 호출하면 사원 정보가 나온다고 가정한다.

<ROOT>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

</ROOT>

 

객체는 Root-List<Employee>의 구조로 생각,

# Root.java 소스

package test;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "ROOT")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
   
    @XmlElement(name="EMPLOYEE")
    private List<Employee> employees;

    public Root() {
        employees = new ArrayList<Employee>();
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

}

 

# Employee.java

package test;

public class Employee {

private String id;
private String nm;

….

… // set, get

}

 

찾고 싶은 사람의 사번을 작성한다.

# XEmployee.java

public class XEmployee {

    private static List<String> employeeList;
   
    public static List<String> getEmployeeList() {
        return employeeList;
    }
   
    static {
        employeeList = new ArrayList<String>();

        employeeList.add("aaa");
         employeeList.add("bbb");
         employeeList.add("ccc");

    }
}

 

찾고 싶은 사람을 찾아서 출력한다.

 

# main 메서드

package test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class CheckEmp {
    public static final boolean isSaved = false;
    public static final String URL = "http://xxxxxxxx";
    public static JAXBContext jaxbContext;
    static {
        try {
            jaxbContext = JAXBContext
                    .newInstance(new Class[] { test.Root.class });
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

        public static void main(String[] args) throws Exception {

        // connect url
        BufferedReader reader = connectURL();

        // save file
        if (isSaved) {
            saveFile(reader);
        }
       

        // get object
        Root root = getRoot(reader);

        // find
        for (String retiredEmpID : XEmployee.getEmployeeList()) {
            boolean found = false;
            for(Employee emp : root.getEmployees()) {
                if (emp.getID().trim().equalsIgnoreCase(retiredEmpID)) {
                    found = true;
                }
            }
            if (found == false) {
                System.out.println(retiredEmpID);
            }
        }
    }

    private static Root getRoot(BufferedReader reader) throws JAXBException {
        Root root = (Root) jaxbContext.createUnmarshaller().unmarshal(reader);
        return root;
    }

    private static BufferedReader connectURL() throws IOException,
            ClientProtocolException, UnsupportedEncodingException {

        HttpGet httpget = new HttpGet(URL);
        HttpClient httpclient = new DefaultHttpClient();
        httpclient.getParams().setParameter("http.socket.timeout", 999000);
       
        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new RuntimeException("url error");
        }
        InputStream instream = entity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(instream, "utf-8"));
        return reader;
    }

    private static void saveFile(BufferedReader reader) throws IOException {
        Writer fstream = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("aaa.txt"), "utf-8"));
        BufferedWriter out = new BufferedWriter(fstream);
        char[] a = new char[1024] ;
        while (reader.read(a) > 0) {
            out.write(a);
        }
        out.flush();
        System.out.println("File created successfully.");
    }

}


# pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>test1123</groupId>
 <artifactId>teswt1</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <dependencies>
  <dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
   <version>4.1.2</version>
  </dependency>
  <dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-xjc</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-impl</artifactId>
   <version>2.2</version>
  </dependency>
 </dependencies>
</project>
Posted by '김용환'
,

일반적으로 자바 배치가 많은 경우에 cpu가 튄다. jvm 단위의

이럴 때는 여러가지를 사용하는데, 
1.  cpulimit 를 쓰는 방법
2. 서버를 여러대로 분산하는 법,
3. 웹 서버로 해서 배치를 만드는 법 (quartz 이용)
4. 스케쥴을 잘 정리할 것

자바는 기본적으로 jvm 시작시, 클래스 로딩, jvm 종료시 서버에 부하를 준다. 따라서 이 작업을 최대한 피하게 하는 것이 좋다. 따라서 10분 단위 미만의 배치는 웹 서버로 바꿔 배치를 돌리는 것이 합리적인 것 같다.
Posted by '김용환'
,


1.  경험
   - 웹 서비스 또는 솔루션을 운영하면서 jvm 옵션을 변경을 다양한 경험 하기 (heap memory, gc algorithm, gc pause, logging)
   튜닝하면서 자연스럽게 인터넷에 흩어져 있는 자료들을 보면서 깊지 않은 공부 (오라클 또는 썬 문서)를 많이 본다.
   - Memory Management in the Java HotSpot™ Virtual Machine (http://java.sun.com/j2se/reference/whitepapers/memorymanagement_whitepaper.pdf)

2. 학습 필요
   - 데니스 리치가 쓴 c programming lanuguage의 chap 8 장 (malloc, free) - 기본 중의 기본.
   - The Garbage Collection Handbook The Art of Automatic Memory Management
   - Garbage Collection: Algorithms for Automatic Dynamic Memory Management
   (운영체제와 컴파일러에 정통하신 분은 금방 보는 참고서일뿐 ......ㅡ.ㅡ;;)

3. 주화입마 될 수 있다. jdk쪽은 좀 복잡함. 머리속을 gc 를 하겠다는 생각으로 봐야함..
   - 오픈 소스 보기
      parrot (https://github.com/parrot/parrot/blob/master/src/gc/gc_gms.c)
      jdk7 (http://hg.openjdk.java.net/jdk7/modules/hotspot/file/9646293b9637/src/share/vm/gc_implementation/)
      qish (http://starynkevitch.net/Basile/qishintro.html )
      mono (http://mono-project.com/Generational_GC, https://github.com/mono/mono/tree/e4b9b8802066f6ed8cca151fe8cddcb4f3262806/mono/metadata 에서 gc.c)
      v8 (http://code.google.com/p/v8/source/browse)의 heap.cc, spaces.cc,  mark-compact.cc
     jikesrvm (http://sourceforge.net/projects/jikesrvm/files/jikesrvm/3.1.1/ )의 rvm\src\org\jikesrvm\mm\mminterface 클래스파일들
 - 논문 보기
     IEEE, ACM, 다양한 paper를 참조하고 reference를 찾아서 보기
     (http://scholar.google.co.kr/scholar?hl=ko&lr=&q=related:ppTY-k29HfoJ:scholar.google.com/&um=1&ie=UTF-8&ei=3Tw1T__XHOGTiQeW2ID2AQ&sa=X&oi=science_links&ct=sl-related&resnum=4&ved=0CF0QzwIwAw)

  
나는 다 보지는 못했고 계속 공부중..

 

Posted by '김용환'
,

 

Oracle Jdk 7부터는 G1 gc 알고리즘을 디폴트로 사용한다고 했었는데.. 인터넷에 이런 자료가 없어서 직접 테스트 해보니 디폴트는 기존의 generation model을 사용중이다. 따로 옵션을 넣어줘야 한다.

 

<서버 설치>

$ mkdir -p /home/www/jdk7

$ cd /home/www/jdk7

$ wget http://download.oracle.com/otn-pub/java/jdk/7/jdk-7-linux-i586.tar.gz

$ tar zxvf jdk-7-linux-i586.tar.gz

 


$ mkdir -p /home/www/tomcat

$ cd /home/www/tomcat

$ wget http://mirror.khlug.org/apache/tomcat/tomcat-7/v7.0.25/bin/apache-tomcat-7.0.25.zip

$ unzip apache-tomcat-7.0.25.zip

 

$ vi /home/www/.bashrc

$ export JAVA_HOME="/home/www/jdk7/jdk1.7.0"

$ source /home/www/.bashrc

 

$ cd /home/www/tomcat/apache-tomcat-7.0.25/bin

$ vi catalina.sh
(다음을 # --- 다음에 추가)
CATALINA_OPTS=" -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=10090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

* bashrc에 넣어둘 수도 있음


$ ./catalina.sh start
Using CATALINA_BASE:   /home/www/tomcat/apache-tomcat-7.0.25
Using CATALINA_HOME:   /home/www/tomcat/apache-tomcat-7.0.25
Using CATALINA_TMPDIR: /home/www/tomcat/apache-tomcat-7.0.25/temp
Using JRE_HOME:        /home/www/jdk7/jdk1.7.0
Using CLASSPATH:       /home/www/tomcat/apache-tomcat-7.0.25/bin/bootstrap.jar:/home/www/tomcat/apache-tomcat-7.0.25/bin/tomcat-juli.jar

 

$ ps -ef | grep java
www      17189     1 97 11:42 pts/1    00:00:02 /home/www/jdk7/jdk1.7.0/bin/java -Djava.util.logging.config.file=/home/www/tomcat/apache-tomcat-7.0.25/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager  -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=10090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.endorsed.dirs=/home/www/tomcat/apache-tomcat-7.0.25/endorsed -classpath /home/www/tomcat/apache-tomcat-7.0.25/bin/bootstrap.jar:/home/www/tomcat/apache-tomcat-7.0.25/bin/tomcat-juli.jar -Dcatalina.base=/home/www/tomcat/apache-tomcat-7.0.25 -Dcatalina.home=/home/www/tomcat/apache-tomcat-7.0.25 -Djava.io.tmpdir=/home/www/tomcat/apache-tomcat-7.0.25/temp org.apache.catalina.startup.Bootstrap start

 

<로컬 환경에 jdk 설치구축>

http://www.oracle.com/technetwork/java/javase/downloads/java-se-jdk-7-download-432154.html

jdk_home에 있는 bin/jconsole.exe 실행

 

<jconsole 이용>

ip와 포트을 입력해서 접속. 디폴트는 jdk 6와 동일하게 사용. 디폴트가 g1을 적용한 것이라고 했지만 young gen/old gen/perm gen 방식을 그대로 사용하는 ps marksweep, ps scavenge를 그대로 이요

 

 

G1 옵션 추가

$ vi catalina.sh
(다음을 # --- 다음에 추가)
CATALINA_OPTS=" -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=10090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false  -XX:+UseG1GC "  

 ( -XX:+UnlockExperimentalVMOptions  는 java 1.6 후반부 버전에서 G1 알고리즘을 쓸떄 사용가능하다. jdk7부터는 이 옵션없이 G1을 쓸 수 있다.)

jconsole로 확인하니, G1 Old gen, G1 young gen으로 뜨는지 확인했다.

chart상으로는 여러 영역이 보이지만, G1 자체가 young/old gen이 없기 때문에 이 정보는 나오지 않는다.








Posted by '김용환'
,

 

지금까지 jdk 6, 7에 항상 일어나는 버그이다.

String [], Byte[]와 같은  Array type ([] )으로 된 클래스를 ClassLoader를 이용해서 클래스 로딩시ClassNotFoundException이 발생한다.

그러나 Class.forName 으로 바꾸면 괜찮다. 이 문제는 reflection을 이용할 때 동일하게 나기 때문에 주의하면서 개발해야 한다.

 

jdk 6, jdk 7에서의 테스트 코드이다.

public class test {
    public static void main(String[] args) throws Exception {
        String[] s = new String[] { "aaa" };
        String clName = s.getClass().getName();
        Class c = test.class.getClassLoader().loadClass(clName);
       System.out.println(c);
    }
}

실행 결과는 다음과 같다.

Exception in thread "main" java.lang.ClassNotFoundException: [Ljava.lang.String;
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    at test.main(test.java:5)

 

문제가 안 일어나게 하려면 다음과 같이 코딩해야 한다.

public class test {
    public static void main(String[] args) throws Exception {
        String[] s = new String[] { "aaa" };
        String clName = s.getClass().getName();
        Class c = Class.forName(clName,false,Thread.currentThread().getContextClassLoader());
        System.out.println(c);
    }
}

결과는 다음과 같다.

class [Ljava.lang.String;

 

이 문제는 “http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6434149”에 올라와 있다.

비슷한 이슈로 다른 버그(“http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627”)도 있다.

public class test {
    public static void main(String[] args) throws Exception {
        String[] objs = new String[10];
        objs[0] = new String();
        String[] objs2 = (String[]) cloneObject(objs,
                test.class.getClassLoader());
    }

    public static Object cloneObject(Object toClone,
            final ClassLoader classLoader) throws Exception {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
        oOut.writeObject(toClone);
        oOut.close();
        ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
        bOut.close();
        ObjectInputStream oIn = new ObjectInputStream(bIn) {
            protected Class resolveClass(ObjectStreamClass desc)
                    throws IOException, ClassNotFoundException {
                System.out
                        .println("Attempting to load class " + desc.getName());
                return classLoader.loadClass(desc.getName());
            }
        };
        bIn.close();
        Object copy = oIn.readObject();
        oIn.close();
        return copy;
    }

}

 

결과.

Exception in thread "main" java.lang.ClassNotFoundException: [Ljava.lang.String;
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    at test.main(test.java:5)

 

이문제는 현재 많은 개발자들은 오라클이 해주기를 기다리고 있다….

오라클 버그 데이터베이스에서는 개발자들이 투표를 통해서 bug를 빨리 처리해달라는 주소도 있으니 참조할 것.
http://bugs.sun.com/bugdatabase/top25_bugs.do

 

Posted by '김용환'
,

 

Webwork를 MVC Framework로, BO, DAO를 Spring으로 쓰다가 Spring 2.X대의 MVC 의 xml bean 설정 때문에 귀찮아했는데.. 그러나 Spring 3.0부터는 annotation을 추가하면서 좋아졌다. (반면 점점 복잡해질 웹 프로젝트의경우는 xml 설정으로 가는 게 더 나을 수 있다. annotation을 쓰면 클래스 이름을 잘 맞춰야 할 것 같다. )

아래 링크를 토대로 Spring 3 MVC Annoataion 기초 내용을 바탕으로 한 자료를 가지고 아주 간단한 maven 프로젝트를 만들었다.

http://www.raistudies.com/spring-mvc-tutorial/
http://www.mkyong.com/spring-mvc/spring-mvc-form-handling-annotation-example/
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html
http://blog.springsource.org/2010/07/22/spring-mvc-3-showcase/
http://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/
http://blog.springsource.org/2009/12/21/mvc-simplifications-in-spring-3-0/

 

> > Spring MVC 3 맛보기 maven 프로젝트 다운

 

 

war 파일 없이 간단하게 mvn clean jetty:run을 이용해서 쉽게 테스트가 가능하다.

Posted by '김용환'
,

 

시작하며..

0.6.4 버전의 카산드라(Cassandra)를 패치한 0.6.5의 큰 변화는 리눅스 운영체제에 동작하는 java 프로세스가 mmap을 사용하면서 swap 메모리가 증가되는 부분을 막기 위함이다.

아래 내용은 이 글을 이해하기 위한 작은 정보이다.

http://knight76.tistory.com/entry/Swap-메모리
http://knight76.tistory.com/entry/자바-RandomAccessFile-클래스의-map-메서드-내부-구조-분석

 

본론..

<이슈와 결과 내용>

0.6.4버전의 카산드라에서 read 성능이 너무 낮아졌고, swap 메모리가 많아지면서 성능이 떨어졌다고 한다.

(https://issues.apache.org/jira/browse/CASSANDRA-1214)

아래와 같이 성능이 어느 정도 나오지 못하고 들쑥날쑥 하고 있다.

JVM의 full gc 시간이 항상 일어나지 않는다. native heap 의 영역의 메모리가 많아져서 swap memory로 갔다가 gc time 때 swap으로부터 메모리를 읽은 후 gc 데이터를 모두 날리는 작업을 하면서 성능이 떨어지는 것이다.

gc 시점 때 all stop + swap in/out이 일어나면서 계속 성능이 저하되는 현상이 생긴 것이다.
(마치 gc 그래프와 흡사한 성능 그래프가 증명하고 있다.)

0.6.5 버전에서는 0.6.5 버전의 패치를 통해서 13%의 성능 향상이 일어났다.

 

<자세한 내용>

카산드라 0.6.4 버전에서는 SSTable read 성능을 높이기 위해서 NIO를 사용했다. 내부 데이터를 MappedByteBuffer 클래스로 읽는다. 이 때 mmap을 사용한다.

storage-conf.xml 설정 파일의 DisAccessMode 속성의 값은 auto이다.

<DiskAccessMode>auto</DiskAccessMode>

 

DatabaseDescritor 클래스의 static 블럭에서 파일을 읽고 있으며 64비트 머신인 경우에는 mmap이 디폴트값으로 정해진다.

String modeRaw = xmlUtils.getNodeValue("/Storage/DiskAccessMode");
diskAccessMode = DiskAccessMode.valueOf(modeRaw);
if (diskAccessMode == DiskAccessMode.auto) {
          diskAccessMode = System.getProperty("os.arch").contains("64") ?
          DiskAccessMode.mmap
: DiskAccessMode.standard;
          indexAccessMode = diskAccessMode;
}

 

disk access mode 의 값이 standard인 경우는 index buffer 자체가 없기 때문에 큰 성능을 기대할 수는 없지만, mmap 모드인 경우에는 index buffer를 메모리에 사용할 수 있다.

SSTableReader 클래스 생성자 에서는 mmap disk access의 경우 mmap을 사용할 수 있도록 되어 있다.

    long indexLength = new File(indexFilename()).length();
    int bufferCount = 1 + (int) (indexLength / BUFFER_SIZE);
    indexBuffers = new MappedByteBuffer[bufferCount];
    long remaining = indexLength;
    for (int i = 0; i < bufferCount; i++)
    {
        indexBuffers[i] = mmap(indexFilename(), i * BUFFER_SIZE,
                                 (int) Math.min(remaining, BUFFER_SIZE));
        remaining -= BUFFER_SIZE;
    }

 

mmap 메서드는 다음과 같이 정의되어 있다.

private static MappedByteBuffer mmap(String filename, long start, int size) throws IOException
{
    RandomAccessFile raf;
    try
    {
        raf = new RandomAccessFile(filename, "r");
    }
    catch (FileNotFoundException e)
    {
        throw new IOError(e);
    }

    try
    {
        return raf.getChannel().map(FileChannel.MapMode.READ_ONLY, start, size);
    }
    finally
    {
        raf.close();
    }
}


RandromAccessFile 클래스의 map 메서드는 내부적으로 clib의 공유할 수 있는 mmap64 시스템콜을 호출하여 native heap 영역의 메모리를 공유한다. (참고 : http://knight76.tistory.com/entry/자바-RandomAccessFile-클래스의-map-메서드-내부-구조-분석)

 

<버그질라 이슈에 대한 설명>

리눅스 커널의 메모리 관리자는 swap 메모리로 언제든지 데이터를 옮길 수 있다.
리눅스 커널의 vm.swappiness 파라미터가 0 이상이면 메모리가 어느 정도 부족하다고 판단하면 swap 메모리로 옮겨놨다가 필요할 때 램에 적재하게 한다.

mmap을 쓰다보니 native heap 영역에 있는 데이터들의 gc 가 되는 시점이 중요해진다.  java의 heap 영역을 청소하는 full gc 가 호출 될 때 unmap이 호출이 되는 구조로 되어 있다. (참고 : http://knight76.tistory.com/entry/자바-RandomAccessFile-클래스의-map-메서드-내부-구조-분석). unmap이 되는 클래스가 PhantomReference를 상속하였는데, 이 의미는 정말 사용 중이지 않을 때 gc 대상이 된다. (전문 용어로 reachable 하지 않거나 referent 객체로부터 로부터 mark되지 않은 상태). soft reference나 weak reference 보다도 최대한 끝까지 살아남는 놈들이다.  (자바 스펙에는 모호하게 적혀 있고, java.lang.ref api와 소스에서 그 의미를 찾아볼 수 있다.)

full gc가 일어나는 시점에서 본다면, native heap 영역이 가득차거나 java heap 영역이 가득찰 때 일어난다. (사실 정확하게 찬다는 의미보다는 gc가 적당한 비율로 메모리를 찼다고 판단하면 gc한다.) 결국은 많은 메모리를 사용 중에 일어날 수 있다.

리눅스 커널은 mmap으로 할당받은 데이터인 SSTable의 정보를 swap 메모리로 이동한다. 무식한 이 데이터는 full gc에도 차곡차곡 쌓이다가 정말 확실히 gc의 대상이 될 때, gc가 된다.
이 때, gc 시점에 정리를 하는데, swap 영역에 있는 녀석이 gc가 될 수 없다. 즉 swap in(ram으로 불러들임)을 한후, gc가 된다. 만약 swap 영역의 메모리가 ram 메모리의 크기보다 많이 크다면 성능은 전혀 기대할 수 없는 수준으로 내려갈 수 밖에 없다.

따라서, 만약 적당한 크기의 swap 영역을 지정할 필요가 있다.  이 영역을 제대로 잡는 것은 계속적인 테스트를 통해서만 할 수 있다.

이런 부분 때문에 카산드라에서는 swaping 셋팅을 전혀 안하는 것이 최고의 선택(best value)라고 하는 배경이다.

http://wiki.apache.org/cassandra/MemtableThresholds

Virtual Memory and Swap

On a dedicated cassandra machine, the best value for your swap settings is no swap at all -- it's better to have the OS kill the java process (taking the node down but leaving your monitoring, etc. up) than to have the system go into swap death (and become entirely unreachable).

Linux users should understand fully and then consider adjusting the system values for swappiness, overcommit_memory and overcommit_ratio.

 

http://wiki.apache.org/cassandra/FAQ#mmap

Why does top report that Cassandra is using a lot more memory than the Java heap max?

Cassandra uses mmap to do zero-copy reads. That is, we use the operating system's virtual memory system to map the sstable data files into the Cassandra process' address space. This will "use" virtual memory; i.e. address space, and will be reported by tools like top accordingly, but on 64 bit systems virtual address space is effectively unlimited so you should not worry about that.

What matters from the perspective of "memory use" in the sense as it is normally meant, is the amount of data allocated on brk() or mmap'd /dev/zero, which represent real memory used. The key issue is that for a mmap'd file, there is never a need to retain the data resident in physical memory. Thus, whatever you do keep resident in physical memory is essentially just there as a cache, in the same way as normal I/O will cause the kernel page cache to retain data that you read/write.

The difference between normal I/O and mmap() is that in the mmap() case the memory is actually mapped to the process, thus affecting the virtual size as reported by top. The main argument for using mmap() instead of standard I/O is the fact that reading entails just touching memory - in the case of the memory being resident, you just read it - you don't even take a page fault (so no overhead in entering the kernel and doing a semi-context switch). This is covered in more detail here.

 

그럼에도 불구하고, 최근에 릴리즈된 1.0.2소스를 보니 DiskAccessMode의 디폴트값이 auto로 되어 있다. 이는니눅스  64 비트 운영체제를 사용하는 신규 개발자에게는 재앙이 될 수 있다는 얘기도 된다.

1.0.2에서는 약간 구조를 바꾸어서 쓰고 있으며, Table .getDataFileLocation() 호출시 적당한 시점에 System.gc()도 및 unmap를 통해서 메모리가 적당히 있을 수 있도록 수정되었다.  (정확히 말하면, 언제부터 바뀐지는 잘 모르지만, 많이 노력하고 있다는 느낌이다.

 

<mlockall의 사용>

0.6.5에서 바뀐 부분은 mlockall 메서드를 사용할 수 있도록 한 부분이다. 이를 위해서 JNA를 사용하였다. (참조 : http://knight76.tistory.com/entry/jni-vs-jna)

ivy.xml 설정 파일에 jna library가 추가되었다.

<dependency org="net.java.dev.jna" name="jna" rev="3.2.7"/>

ivysettings 설정 파일이 추가되었다.

<ivysettings>
  <settings defaultResolver="ibiblio"/>
  <resolvers>
    <chain name="chain" dual="true">
      <ibiblio name="java.net2" root="http://download.java.net/maven/2/" m2compatible="true"/>
      <ibiblio name="ibiblio" m2compatible="true" />
    </chain>
  </resolvers>
  <modules>
    <module organisation="net.java.dev.jna" name="jna" resolver="chain" />
  </modules>
</ivysettings>

 

CassandraDaemon 클래스의 setup 메서드안에서  FBUtilities.tryMlockall(); 을 호출한다. tryMlockall() 에서는 clib의 mlockAll() 시스템 콜을 호출한다. 즉 처음부터 nio 로 만든 객체들을 swap 메모리로 가지 않게 하고, ram에서만 사용할 수 있도록 바뀐 것이다.


public static void tryMlockall()
{
    int errno = Integer.MIN_VALUE;
    try
    {
       int result = CLibrary.mlockall(CLibrary.MCL_CURRENT);
        if (result != 0)
            errno = Native.getLastError();
    }
    catch (UnsatisfiedLinkError e)
    {
        // this will have already been logged by CLibrary, no need to repeat it
        return;
    }
    catch (Exception e)
    {
        logger_.debug("Unable to mlockall", e);
        // skipping mlockall doesn't seem to be a Big Deal except on Linux.  See CASSANDRA-1214
        if (System.getProperty("os.name").toLowerCase().contains("linux"))
        {
            logger_.warn("Unable to lock JVM memory (" + e.getMessage() + ")."
                         + " This can result in part of the JVM being swapped out, especially with mmapped I/O enabled.");
        }
        else if (!System.getProperty("os.name").toLowerCase().contains("windows"))
        {
            logger_.info("Unable to lock JVM memory: " + e.getMessage());
        }
        return;
    }

    if (errno != Integer.MIN_VALUE)
    {
        if (errno == CLibrary.ENOMEM && System.getProperty("os.name").toLowerCase().contains("linux"))
        {
            logger_.warn("Unable to lock JVM memory (ENOMEM)."
                         + " This can result in part of the JVM being swapped out, especially with mmapped I/O enabled."
                         + " Increase RLIMIT_MEMLOCK or run Cassandra as root.");
        }
        else if (!System.getProperty("os.name").toLowerCase().contains("mac"))
        {
            // OS X allows mlockall to be called, but always returns an error
            logger_.warn("Unknown mlockall error " + errno);
        }
    }
}

 

이런 용도로 CLibrary 클래스가 추가되었다.

public final class CLibrary
{
    private static Logger logger = LoggerFactory.getLogger(CLibrary.class);

    public static final int MCL_CURRENT = 1;
    public static final int MCL_FUTURE = 2;
   
    public static final int ENOMEM = 12;

    static
    {
        try
        {
            Native.register("c");
        }
        catch (NoClassDefFoundError e)
        {
            logger.info("JNA not found. Native methods will be disabled.");
        }
        catch (UnsatisfiedLinkError e)
        {
            logger.info("Unable to link C library. Native methods will be disabled.");
        }
    }

    public static native int mlockall(int flags);

    public static native int munlockall();

    private CLibrary() {}
}

 

참고로 munlockall() 메서드도 native로 바인딩되었지만 1.0.2에서도 실제 사용하지 않고 있다.

 

 

마치며..

2일 동안 카산드라의 파일 시스템쪽이 어떻게 되어 있는지 잘 몰랐는데. 이번 기회에 카산드라의 mmap 이슈와 그 처리방법에 대해서 잘 파악할 수 있었다. 시간이 되면 카산드라 분석해볼까나…

 

 

 

* 참고 내용 : mlock, mlockall (man 페이지를 이용한 정보 처리)


mlock, mlockall, munlock, munlockall 시스템 콜 함수에 대한 정리
"man mlock" 명령어를 사용하면 mlock 에 대한 정보를 확인할 수 있다.

       #include <sys/mman.h>

       int mlock(const void *addr, size_t len);

       int munlock(const void *addr, size_t len);

       int mlockall(int flags);

       int munlockall(void);

man mlock 에 대한 결과값을 확인할 수 있다.

mlock() 또는 mlockall()은 swap 영역으로 메모리 이동이 되지 않도록 RAM 안에 사용된 virtual memory(가상 주소)를 lock 한다.
munlock() 또는 munlockall()은 리눅스 커널 메모리 관리자에 의해서 swap out될 수 있도록 unlock해주는 함수이다.

mlock() 함수는 주소와 길이에 대한 영역을 lock을 개런티한다.
mlockall() 함수는 프로세스에 대한 주소번지 모두를 lock 한다.
코드/데이터/스택/동적라이르리러,사용자 커널데이터/공유메모리/메모리맵 데이터까지모두 lock한다.

mlockall() 의 아규먼트는 int형 타입의 flag 값을 지정하면 된다. 아래 타입 중에 하나만 또는 OR로 사용가능하다.

- MCL_CURRENT (1): 프로세스의 주소 영역으로 매핑된 모든 페이지
- MCL_FUTURE (2) : 앞으로 프로세스의 주소 영역에 매필될 모든 페이지.
               이 옵션을 사용하면 메모리 할당과 같은 시스템 콜(mmap, sbrk, malloc)을 쓰다 RAM 영역을 넘어설때 할당을 해주지 못하게 된다.
               만약 Stack에서 그 일이 발생하면, SIGSEGV signal이 발생하고 stack 영역은 확장을 할 수 없다.

 

Memory locking은 두 개의 주요 어플리케이션에 사용할 수 있다. real time 알고리즘과 보안을 높이 필요로 하는 데이터 처리쪽에 사용할 수 있다.
특히 real time 알고리즘이 필요한 어플리케이션에서는 locking 함으로서, page fault를 원천적으로 봉쇄해서
time-critical section(시간 동기 문제)가 일어나지 않게 할 수 있다.

 

Cassandra 0.6.5~1.0.2 (최신) 까지 mlockall(MCL_CURRENT)을 주어 사용하고 있음. 따라서 언제든지 차곡차곡 쌓이는 일부 메모리는 swap 메모리로 늘어날 수 있음..

Posted by '김용환'
,