Jboss Clustering

|

http://community.jboss.org/wiki/TwoClustersontheSameNetwork


------------------------------------------




Created on: Oct 5, 2005 9:36 AM by Stan Silvert - Last Modified:  Oct 16, 2009 12:32 PM by Brian Stansberry

Note: this document is intended as JBoss Application Server documentation and not as general JGroups documentation. Some of the content may be useful in a general sense, but it is written from the point of view of configuring JBoss AS clusters.

I want to have two JBoss AS clusters on the same network and I don't want them to see each other

 

First, what do we mean by a cluster here? Short answer: we're talking about a JGroups Channel.

 

Longer answer: We're talking about a service, an instance of which is deployed on several JBoss AS instances, where the different instances communicate with each other in order to provide a unified view to clients.

 

Within the AS, there are a number such services -- 3 different JBoss Cache services (used for HttpSession replication, EJB3 SFSB replication and EJB3 entity replication) along with a general purpose clustering service called HAPartition that underlies most other JBossHA services.

 

Each instance of these services creates a JGroups Channel and uses it to communicate with its peers.

 

In AS 5 or EAP 4.3 the JBoss Messaging service opens a further two channels.

 

It is critical that these channels only communicate with their intended peers; not with the channels used by other services and not with channels for the same service opened on machines not meant to be part of the group.

 

Isolating JGroups Channels

 

A JGroups Channel is defined by its group name, multicast address, and multicast port.

 

To isolate JGroups channels for different services on the same set of AS instances from each other, you MUSTuse a unique group name for each and either 1) use a different multicast port for each or 2) have the channels share a JGroups shared transport (available beginning with AS 5.0.0) .


For example, say we have a production cluster of 3 machines, each of which has an HAPartition deployed along with a JBoss Cache used for web session clustering.  The HAPartition channels should not communicate with the JBoss Cache channels.  Those channels should use a different group name and should either use a different multicast port or share a JGroups transport. They can use the same multicast address, although they don't need to. The default configs that ship in AS 4.0.4 and later will use different group names and multicast ports for the standard AS clustering services, while in AS 5.0.0 and later they will use a shared transport. So isolating the channels for different services requires no effort.

 

To isolate JGroups channels for the same service from other instances of the service on the network, youMUST change ALL three values on 4.x and earlier. Each channel must have its own group name, multicast address, and multicast port. Due to improvements in JGroups, in AS 5.x and later it is sufficient to change just the group name and multicast address; changing multicast ports should not be necessary in AS 5.

 

For example, say we have a production cluster of 3 machines, each of which has an HAPartition deployed.  On the same network there is also a QA cluster of 3 machines, which also has an HAPartition deployed. The HAPartition group name, multicast address, and multicast port (in AS 4.x and earlier) for the production machines must be different from those used on the QA machines.

 

Changing the Group Name

 

The group name for a JGroups channel is configured via the service that starts the channel.  Unfortunately, different services use different attribute names for configuring this. For HAPartition and related services configured in the deploy/cluster-service.xml file (deploy/cluster/hapartition-jboss-beans.xml in AS 5), this is configured via a PartitionName attribute.  For JBoss Cache services, the name of the attribute isClusterName.

 

Starting with JBoss AS 4.0.2, you can easily change the cluster-service.xml PartitionName by specifying it as a system property at startup.

 

./run.sh -c all -Djboss.partition.name=MyPartition

 

Starting with JBoss AS 4.0.3, you can use a command line switch instead of setting a system property:

 

./run.sh -c all -g MyPartition

 

or

 

./run.sh -c all --partition MyPartition

 

Behind the scenes, both of these set the jboss.partition.name system property.

 

See Changing a Cluster PartitionName for more on this.

 

Starting with JBoss AS 4.0.4, the value passed at startup via -g or --partition or -Djboss.partition.nameis used as part of the ClusterName used by JBoss Cache for the web session replication cache.  For earlier releases, the tc5-cluster-service.xml file can easily be modified to provide this behavior:

 

<attribute name="ClusterName">Tomcat-${jboss.partition.name:Cluster}</attribute>

 

Changing the Multicast Address

 

Starting with JBoss AS 4.0.3, the jboss.partition.udpGroup system property can be used to control the multicast address used by the JGroups channel opened by all standard AS services. A command line option to set this property was also added:

 

./run.sh -c all -u 233.3.4.5

 

or

 

./run.sh -c all --udp 233.3.4.5

 

Behind the scenes, both of these set the jboss.partition.udpGroup system property.

 

Changing the Multicast Port

 

(Note: improvements in JGroups mean changing multicast ports should not be necessary in AS 5.)

 

Changing the multicast port involves changing the protocol stack configuration for the various JGroups channels. The configuration for any JGroups channel that uses the UDP or MPING protocols should be changed. (MPING is not used in any of the standard protocol stack configurations in AS 4.x or 3.x; it is used in AS 5).  With both UDP and MPING, it is the mcast_port attribute that needs to be changed:

 

.... <config>    <UDP mcast_addr="${jboss.partition.udpGroup:228.1.2.3}"         mcast_port="45566"         ip_ttl="8" ip_mcast="true"         mcast_send_buf_size="800000" mcast_recv_buf_size="150000"         ucast_send_buf_size="800000" ucast_recv_buf_size="150000"         loopback="false"/>     ....

 

MPING is used in a TCP-based stack:

 

.... <config>    <TCP start_port="7600"         ....    />    <MPING timeout="3000"           num_initial_members="3"           mcast_addr="${jboss.partition.udpGroup:230.11.11.11}"           mcast_port="45700"           ip_ttl="${jgroups.udp.ip_ttl:2}"/>   ....

 

Where the JGroups channel configurations are located varies between AS versions:

 

JBoss AS 5

 

Improvements in JGroups mean changing multicast ports should not be necessary in AS 5. However, the following information on how to change multicast ports in AS 5 is provided here as general documentation.

 

In AS 5, all JGroups protocol stack configuration is isolated in a single file: /deploy/cluster/jgroups-channelfactory.sar/META-INF/jgroups-channelfactory-stacks.xml. This file contains a number of protocol stack configurations used by the AS's JGroups ChannelFactory. Not all stacks described in that file are actually used; by default in AS 5.0.0 only the udpjbm-control and jbm-data stacks are used. (Services like the clustered HttpSession cache that use JGroups can be configured to use other stacks in the file besides those three default ones.)  There is no need to change the ports for stacks that you are not using.

 

It shouldn't be necessary to actually modify this file to control the ports used; this is because each port configuration uses system property substitution syntax to allow you to set the property from the command line using -D. For example, the UDP protocol mcast_port for the udp and jbm-control stacks is configured via thejboss.jgroups.udp.mcast_port system property:

mcast_port="${jboss.jgroups.udp.mcast_port:45688}"

 

while the MPING protocol mcast_port in the jbm-data stack uses:

mcast_port="${jboss.messaging.datachanneludpport:45710}"

 

The jboss.jgroups.udp.mcast_port property is a bit of a special case; it can be set from the command line using the shorthand -m command line option, e.g.:

 

./run.sh -c all -m 54321 -Djboss.messaging.datachanneludpport=12345
4.2.x and Earlier

 

In AS 4.2.x and earlier, JGroups channel configurations are embedded in the configurations for the services that use the channel.

 

For the HAPartition service, the JGroups channel configuration is found in deploy/cluster-service.xml.

 

Fort HttpSession replication, a JGroups channel configuration is found in deploy/tc5-cluster-service.xml. (In 4.0.4 and 4.0.5, this is instead located in deploy/tc5-cluster.sar/META-INF/jboss-service.xml. In the 4.2 series it is located in deploy/jboss-web-cluster.sar/META-INF/jboss-service.xml).

 

For EJB3 clustering, JGroups channel configurations are found in the deploy/ejb3-clustered-sfsbcache-service.xml and deploy/ejb3-entity-cache-service.xml files.

 

For clustered JBoss Messaging in EAP 4.3, JGroups channel configurations are found in the deploy/jboss-messaging.sar/clustered-hsqldb-persistence-service.xml file.

Configuration via System Property in 4.x

 

In the AS 4.2 and EAP 4.x releases, the mcast_port configurations for the services noted above can all be controlled via a system property by using -D when starting JBoss.The properties and the services they configure are as follows:

 

System Property
Service
jboss.hapartition.mcast_port HAPartition (cluster-service.xml)
jboss.webpartition.mcast_port HttpSession clustering (jboss-web-cluster.sar)
jboss.ejb3entitypartition.mcast_port EJB3 Entity Clustered Second Level Cache (ejb3-entity-cache-service.xml)
jboss.ejb3sfsbpartition.mcast_port EJB3 SFSB clustering (ejb3-clustered-sfsbcache-service.xml)
jboss.messaging.datachanneludpport JBoss Messaging Data Channel (jboss-messaging.sar)
jboss.messaging.controlchanneludpport JBoss Messaging Control Channel (jboss-messaging.sar)

 

For releases prior to 4.2, you can also define the multicast ports via system properties but you need to modify the corresponding xml file to use system property substitution. For this example, we have chosen cluster-service.xml:

 

         ....          <Config>             <UDP mcast_addr="${jboss.partition.udpGroup:228.1.2.3}"                mcast_port="${jboss.hapartition.mcast_port:45566}"                ip_ttl="8" ip_mcast="true"                mcast_send_buf_size="800000" mcast_recv_buf_size="150000"                ucast_send_buf_size="800000" ucast_recv_buf_size="150000"                loopback="false"></UDP>          ....

 

You will have to do a similar thing for any clustered service, such as HTTP session replication, EJB3 entity bean cache...etc. Use a different system property for each. Please, do not use the same port for all services due to the problems explained in the promiscuous traffic wiki.

 

Isolating Channels in JBoss AS 5

 

An excellent piece of news is that isolating clusters in AS 5.0.1 should no longer require changing multicast ports. Actually, simply starting each cluster with a different value passed to -u (as described above) should be sufficient. Even setting different partition names via -g isn't absolutely required (altough it's simple to do and still recommended.) As the above process of changing the multicast ports is by far the most cumbersome aspect of cluster isolation this is good news indeed.

 

In JGroups 2.6.5 and 2.6.6 fixes were made to the UDP and MPING protocols that resolve the issue that lead to the need to change multicast ports.

 

And an added bit of good news is a simple tweak to correct a mistake in the default AS 5.0.0.GA configuration will allow you to skip changing multicast port values in AS 5.0.0.GA as well.  The correction to AS 5.0.0.GA involves a simple edit to the $JBOSS_HOME/server/all/deploy/cluster/jgroups-channelfactory.sar/META-INF/jgroups-channelfactory-stacks.xml file to edit the jbm-data stack configuration:

 

<stack name="jbm-data"        description="Stack optimized for the JBoss Messaging Data Channel">   <config>      <TCP singleton_name="jbm-data"           start_port="${jboss.messaging.datachanneltcpport:7900}"            .....            oob_thread_pool.queue_max_size="100"            oob_thread_pool.rejection_policy="run"/>      <MPING timeout=5000             mcast_addr=${jboss.messaging.datachanneludpaddress,jboss.partition.udpGroup:228.6.6.6}             .....

 

Change the MPING.mcast_addr config to:

 

mcast_addr=${jboss.partition.udpGroup:228.6.6.6}

 

Basically, the JGroups XML parser doesn't handle the more complex system property substitution syntax, so reduce it to the simpler syntax. Eliminate the first system property, not the second.

 

There are several other places in the jgroups-channelfactory-stacks.xml file that have the same problem, although this is the only one that causes problems with a default JBoss AS 5.0.0.GA. But, fixing making the same fix to the others is recommended as well.

 

This problem will be eliminated in the first release after AS 5.0.0.GA.

 

Other Notes

Using ip_ttl to isolate clusters

 

Another tip: if your clusters are on different subnets, configure the switches between your VLANs/subnets to drop IP multicast packets. If that is not an option, you can achieve much the same thing by reducing the UDP (and MPING) protocol's ip_ttl to a value where the multicast doesn't leave the subnet, e.g. 2.

 

Beginning with JBoss AS 4.0.5, if you set the jgroups.mcast.ip_ttl system property, that value will be picked up by the standard JGroups channel configurations.

 

This is sometimes a useful technique for developer workstations as well; i.e. set ip_ttl to 0 or 1 to prevent developers' workstations from clustering with each other. This doesn't always work though; it depends on how the specific network hardware deals with the TTL.  Another approach for isolating developer workstations is to start JBoss with -b localhost.

Why isn't it sufficient to change the group name?

 

If channels with different group names share the same multicast address and port, the lower level JGroups protocols in each channel will see, process and eventually discard messages intended for the other group.  This will at a minimum hurt performance and can lead to anomalous behavior.

 

Why isn't it sufficient to change the group name and multicast address? Why do I need to change the multicast port if I change the address?

 

First off, beginning with JGroups 2.6.6 it probably isn't necessary. But for those using earlier JGroups releases, or those interested in details...

 

There is an issue on several operating systems whereby packets addressed to a particular multicast port are delivered to all listeners on that port, regardless of the multicast address they are listening on.  If you know your OS doesn't have this issue, only changing the multicast address is sufficient.  See PromiscuousTrafficfor more on this, including a test you can run to see if your OS exhibits this behavior.

 

In JGroups 2.6.5 and 2.6.6 there are fixes to the JGroups UDP and MPING protocols such that on Linux systems multicast sockets are created in such a way that the promiscuous traffic problem doesn't occur. AnInetAddress indicating the multicast address is passed to the java.net.MulticastSocket constructor; that avoids the problem. However, constructing a MulticastSocket that way on other systems will lead to failures, so before doing this JGroups checks the os.name system property to confirm that Linux is the OS.

 

EJB clients

 

If you have EJB clients, see this forum post for info on restricting EJB clients to a particular partition.

 

The jboss.partition.udpGroup and jboss.partition.udpPort System Properties

 

There are two special system properties,  and , that JGroups will programatically check as part of the process of setting up a channel.  If the former is found, the value of the property will be used to set the channel's multicast address, overriding any value set in the configuration file.  If the latter is found the value of the property will be used to set the channel's multicast port, again overriding any value set in the configuration file.

 

Do not set both of these properties!!! If you do, all the channels on the server will use the same address/port combination and will thus see each others' traffic.  We recommend setting , which is automatically done if you start JBoss AS using the  switch.  If you do that, don't set .

신고

'개발/활용정보 > Java' 카테고리의 다른 글

Creating a Custom Event  (0) 2011.04.19
eclipse 단축키  (0) 2011.04.13
자바가 사용하는 메모리의 종류와 특징  (0) 2011.04.13
Java 메모리 설정 MaxPermSize  (0) 2011.04.13
Jboss Clustering  (0) 2011.04.13
Java theory and practice: 메모리 누수와 약한 참조  (0) 2011.04.13
Trackback 0 And Comment 0

Java theory and practice: 메모리 누수와 약한 참조

|

http://www.ibm.com/developerworks/kr/library/j-jtp11225/


가비지 컬렉터가 프로그램에서 더 이상 사용되지 않는 객체들을 처리하려면 객체의 논리적 수명(애플리케이션이 객체를 사용하는 시간)과 그 객체에 대한 레퍼런스의 실제 수명이 같아야 한다. 대부분의 경우 이 일은 자동으로 처리되기 때문에 우리가 객체의 수명 문제까지 신경 쓰지 않아도 된다. 하지만 가끔씩은 우리가 예상했던 것 보다 훨씬 긴 시간동안 메모리에 객체를 보유하고 있는 레퍼런스를 만들 때가 있다. 이 상황을 의도하지 않은 객체유지(unintentional object retention)라고 한다.

메모리 누수와 글로벌 맵

의도하지 않은 객체 유지의 가장 일반적인 원인은 Map을 사용하여 메타데이터와 임시 객체들을 연결하는데 있다. 예를 들어, 클라이언트로부터의 소켓 연결 같은 중간 수명(메소드 호출 보다는 길지만 애플리케이션 보다는 짧은 수명)을 가진 객체가 있다고 가정해 보자. 몇 개의 메타데이터를 그 소켓과 연결해야 한다 그 당시에는 Socket이 만들어진다는 사실을 모르고, Socket 클래스나 인스턴스를 제어할 수 없기 때문에 Socket 객체로 데이터를 추가할 수 없다. 이 경우, 전형적인 방식은 그와 같은 정보를 글로벌 Map에 저장하는 것이다. Listing 1의 SocketManager 클래스를 보자.


Listing 1. 글로벌 Map을 사용하여 메타데이터와 객체 제휴하기 
public class SocketManager {
    private Map<Socket,User> m = new HashMap<Socket,User>();
    
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
    public void removeUser(Socket s) {
        m.remove(s);
    }
}

SocketManager socketManager;
socketManager.setUser(socket, user);

이 방식의 문제점은 메타데이터의 수명은 소켓의 수명과 연관되어 있는데, 개발자가 정확히 언제 소켓이 더 이상 프로그램에서 필요하지 않은지를 알고, Map에서 상응 매핑을 제거해야 한다는 것을 기억하지 못하면, Socket과 User 객체들은 Map에 영원히 머무르게 된다는 점이다. 이것 때문에 Socket과 User 객체들이 가비지 컬렉팅이 되지 못한다. 바로 이것이 꽤 오랫동안 메모리에서 프로그램이 실행되는 원인이 된다. 프로그램에서 더 이상 Socket이 필요하지 않을 때를 찾아내는 것은 수동적인 메모리 관리를 필요로 하므로 성가시면서도 에러를 일으킬 소지가 있는 기술이다.

메모리 누수 규명하기

프로그램이 메모리 누수 현상을 겪고 있다는 첫 번째 신호는 시스템이 OutOfMemoryError를 던지거나 빈번한 가비지 컬렉션으로 인해서 퍼포먼스가 나빠지기 시작하는 것이다. 다행히도 가비지 컬렉터는 메모리 누수를 진단할 때 사용할 수 있는 많은 정보들을 공유하고 있다. -verbose:gc 또는 -Xloggc 옵션으로 JVM을 호출하면 GC(garbage collection)가 실행하면서 진단 메시지를 콘솔 또는 로그 파일에 프린트 한다. 걸리는 시간, 현재 힙 사용, 복구된 메모리 등을 프린트한다. GC 사용을 기록하는 것은 그렇게 매력적인 일은 아니기 때문에 메모리 문제를 분석하거나 가비지 컬렉터를 튜닝해야 할 경우 제품에서 기본적으로 GC 로깅을 실행하는 것이 바람직하다.

툴들은 GC 로그 아웃풋을 취해 이를 그래픽으로 디스플레이 한다. 그 툴 중 하나가 프리 JTune(참고자료)이다. GC 후에 힙 사이즈의 그래프를 보면 프로그램의 메모리 사용 내용을 볼 수 있다. 대부분의 프로그램의 경우 메모리 사용을 두 개의 컴포넌트 (기본(baseline) 사용과 현재 로드(current load) 사용)로 나눌 수 있다. 서버 애플리케이션의 경우, 기본(baseline) 사용은 어떤 부하에도 종속되지는 않지만 요청을 받아들일 준비가 될 때 애플리케이션이 기본적으로 사용하는 것이다. 현재 로드 사용은 요청을 처리하는 프로세스에 사용되지만 요청 프로세싱이 완료되면 사라지는 것이다. 부하가 일정하면 애플리케이션은 일정하게 메모리를 사용한다. 애플리케이션이 초기화를 완료하고 부하가 증가하지 않는데도 메모리 사용 추세가 계속 상승되면, 이 프로그램은 아마도 이전 요청을 처리하는 과정에서 만들어진 객체를 보유하고 있는 것이 분명하다.

Listing 2는 메모리 누수가 있는 프로그램이다. MapLeaker는 쓰레드 풀에서 태스크룰 처리하고 각 태스크의 상태를 Map에 기록한다. 안타깝게도 태스크가 완료될 때 엔트리를 삭제하지 않기 때문에 상태 엔트리와 태스크 객체가(내부 상태와 함께) 영원히 축적된다.


Listing 2. Map 기반 메모리 유출을 가진 프로그램 
public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map<Task, TaskStatus> taskStatus 
        = Collections.synchronizedMap(new HashMap<Task, TaskStatus>());
    private Random random = new Random();

    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };

    private class Task implements Runnable {
        private int[] numbers = new int[random.nextInt(200)];

        public void run() {
            int[] temp = new int[random.nextInt(10000)];
            taskStatus.put(this, TaskStatus.STARTED);
            doSomeWork();
            taskStatus.put(this, TaskStatus.FINISHED);
        }
    }

    public Task newTask() {
        Task t = new Task();
        taskStatus.put(t, TaskStatus.NOT_STARTED);
        exec.execute(t);
        return t;
    }
}

그림 1은 GC 후에 MapLeaker에 대한 애플리케이션 힙 사이즈를 나타낸 그래프이다. 그래프가 계속 올라가고 있다는 것은 메모리 누수의 증거이다. (실제 애플리케이션에서, 이 슬로프는 이런 양상을 띄지는 않는다. 하지만 GC 데이터를 오랫동안 모으면 이런 모습이 된다.)


그림 1. 지속적으로 상승하는 메모리 사용
 

메모리 누수를 감지했다면 어떤 유형의 객체들이 문제를 일으키는지를 찾아낸다. 어떤 메모리 프로파일러로 객체 클래스로 나뉘어진 힙의 스냅샷을 만들 수 있다. 상용 힙 프로파일링 툴도 사용할 수 있지만 메모리 누수를 찾는데 돈까지 들일 필요는 없다. 빌트인 hprof툴이 이 트릭을 수행한다. hprof를 사용하여 메모리 사용을 트래킹하도록 하려면 -Xrunhprof:heap=sites 옵션으로 JVM을 호출한다.

Listing 3은 애플리케이션의 메모리 사용 부분을 나타내는 hprof 아웃풋이다. 애플리케이션이 종료한 후 또는 Windows상에서 Ctrl+Break을 누르거나 kill -3으로 애플리케이션에 신호를 보낼 때 hprof 툴은 아웃풋을 만든다.

Listing 3보기. (Listing 3. Map.Entry와 Task 객체에서 증가 추세를 보이는 hprof 아웃풋)

Listing 4는 hprof 아웃풋의 또 다른 부분을 보여준다. Map.Entry 객체용 할당 사이트용 콜 스택 정보를 제공한다. 이 아웃풋은 어떤 호출 체인이 Map.Entry 객체들을 생성하고 있는지를 말해준다. 프로그램 분석과 더불어 이것은 매우 쉽게 메모리 누수의 원인을 알아낼 수 있는 방법이다.


Listing 4. Map.Entry 객체용 할당 사이트를 보여주는 HPROF 아웃풋 
TRACE 300446:
 java.util.HashMap$Entry.<init>(<Unknown Source>:Unknown line)
 java.util.HashMap.addEntry(<Unknown Source>:Unknown line)
 java.util.HashMap.put(<Unknown Source>:Unknown line)
 java.util.Collections$SynchronizedMap.put(<Unknown Source>:Unknown line)
 com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
 com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)

약한 참조(weak reference)

SocketManager의 문제는 Socket-User 매핑의 수명이 Socket의 수명과 매치해야 하는데 이 규칙을 실행할 쉬운 방식을 제공하는 언어가 없다는 점이다. 프로그램은 수동 메모리 관리와 비슷한 기술에 의존하게 된다. 다행히도 JDK 1.2 부터, 가비지 컬렉터가 약한 참조(weak references)를 통해 이러한 유형의 메모리 누수를 방지하도록 한다.

약한 참조는 레퍼런트(referent)라고 하는 객체 레퍼런스를 위한 홀더(holder)이다. 약한 참조를 사용하여 레퍼런트에 대한 참조를 관리할 수 있다.(가비지 컬렉팅이 가능하다.) 가비지 컬렉터가 힙을 트레이스 할 때, 객체에 대한 대표적인 레퍼런스가 약한 참조라면 레퍼런트는 GC의 대상이 된다. (약한 참조에 의해 유일하게 참조되는 객체를 weakly reachable이라고 한다.)

약한 참조의 레퍼런트는 구현 시 설정되고 이 값이 제거되지 않았다면 이 값은 get()으로 검색된다. (레퍼런트가 이미 가비지 컬렉팅 되었기 때문이거나 누군가가 WeakReference.clear()를 호출하여) 약한 참조가 제거되면 get()은 null을 리턴한다. 따라서 get()이 비 null 값을 리턴하는지 언제나 검사해야 한다. 레퍼런트는 결국 가비지 컬렉팅 될 것이기 때문이다.

일반 참조(strong reference)를 사용하여 객체 레퍼런스를 복사할 때 레퍼런트의 수명이 최소 복사된 레퍼런스의 수명만큼 되도록 제한한다. 객체에 대해 약한 참조를 구현하면 레퍼런트의 수명을 확장하지 않는다. 그저 이것이 살아있는 동안 만 관리하면 된다.

약한 참조는 약한 컬렉션(weak collection)들을 구현할 때 가장 유용하다. 이것은 정확히 SocketManager 클래스가 해야 할 일이다. 일반적인 약한 참조 사용이기 때문에, 키에 약한 참조를 사용하는 WeakHashMap도 역시 JDK 1.2에 있는 클래스 라이브러리에 추가되었다. 일반 HashMap에 키로서 객체를 사용하면 그 객체는 Map에서 매핑이 제거될 때 까지 컬렉팅 되지 않는다. WeakHashMap을 사용하여 객체가 가비지 컬렉팅 되는 것을 방지하지 않고도 Map 키로서 객체를 사용할 수 있다. Listing 5는 WeakHashMap에서의 get() 메소드의 가능한 구현이다. (약한 참조를 보여준다.)


Listing 5. WeakReference.get()의 구현 
public class WeakHashMap<K,V> implements Map<K,V> {

    private static class Entry<K,V> extends WeakReference<K> 
      implements Map.Entry<K,V> {
        private V value;
        private final int hash;
        private Entry<K,V> next;
        ...
    }

    public V get(Object key) {
        int hash = getHash(key);
        Entry<K,V> e = getChain(hash);
        while (e != null) {
            K eKey= e.get();
            if (e.hash == hash && (key == eKey || key.equals(eKey)))
                return e.value;
            e = e.next;
        }
        return null;
    }

WeakReference.get()이 호출되면(여전히 살아있다면) 레퍼런트에 대한 일반 참조를 리턴하기 때문에 while 루프의 바디에서 사라지는 매핑에 대해 걱정할 필요가 없다. 일반 참조는 가비지 컬렉팅 되는 것으로부터 이를 방지한다. WeakHashMap의 구현은 약한 참조를 가진 일반 이디엄을 의미한다. 몇몇 내부 객체는 WeakReference를 확장한다.

WeakHashMap에 매핑을 추가하면 키가 가비지 컬렉팅 되기 때문에 나중에 이 매핑은 빠질 수 있다. 결과적으로 get()은 null을 리턴하면서 null용 get()의 리턴 값을 테스트하는 것이 더 중요해진다.

누수와 WeakHashMap 연결하기

SocketManager 에서의 유출을 픽스하는 것은 쉽다. HashMap을 WeakHashMap으로 대체하면 된다.(Listing 6) (SocketManager가 쓰레드 보안이 되어야 한다면 WeakHashMap을 Collections.synchronizedMap()으로 래핑할 수 있다.) 매핑의 수명이 키의 수명과 연결되어야 할 때마다 이 방식을 사용할 수 있다. 하지만 이 기술을 남용하지 않도록 조심한다. 대부분의 경우에는 일반 HashMap은 올바른 Map 구현으로서 사용하는 것이 옳기 마련이다.


Listing 6. SocketManager를 WeakHashMap으로 픽스하기
public class SocketManager {
    private Map<Socket,User> m = new WeakHashMap<Socket,User>();
    
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
}

레퍼런스 큐 (Reference queues)

WeakHashMap은 맵 키를 보유하기 위해 약한 참조를 사용한다. 따라서 키 객체들이 애플리케이션에서 더 이상 사용되지 않을 때 이것을 가비지 컬렉팅 되도록 하고, WeakReference.get()이 null을 리턴하는지의 여부에 따라 get() 구현은 죽은 매핑과 살아있는 매핑을 구별한다. 하지만 이것은 get()의 메모리 사용이 애플리케이션의 수명 동안 늘어나는 것을 방지하는데 필요한 것의 절반 정도에 지나지 않는다. 어떤 것은 키 객체가 컬렉팅 된 후 Map에서 죽은 엔트리를 없애는데도 수행될 수 있다. Map은 상응하는 죽은 키들에 대한 엔트리들로 채운다. 그리고 이것이 애플리케이션에 보이지 않는 동안 Map.Entry와 값 객체가 컬렉팅되지 않도록 하기 때문에 메모리 밖에서 애플리케이션을 실행시키는 원인이 된다.

주기적으로 Map을 스캐닝하고, 각각의 약한 참조에 get()을 호출하고 get()이 null을 리턴하면 그 매핑을 제거하는 방식을 통해 죽은 매핑이 제거될 수 있다. 약한 참조의 레퍼런트가 가비지 컬렉팅될 때 공지가 될 수 있는 방법이 있다면 좋을 것이다. 바로 이 일을 레퍼런스 큐(reference queues)가 수행한다.

레퍼런스 큐는 정보를 객체의 라이프 사이클 정보를 애플리케이션에 피드백하는 가비지 컬렉터의 기본적인 수단이다. 약한 참조에는 두 개의 구조체가 있다. 하나는 인자로서 레퍼런트만 취하고 다른 하나는 레퍼런스 큐를 취한다. 약한 참조는 관련 레퍼런스 큐와 함께 생성되고 레퍼런트가 GC의 후보가 되면 레퍼런스 객체(레퍼런트가 아님)는 레퍼런스가 깨끗해진 후에 레퍼런스 큐에 인큐(enqueue) 된다. 이 애플리케이션은 레퍼런스 큐에서 레퍼런스를 검색하여 레퍼런트가 컬렉팅 되었다는 것을 알면 관련 클린업 액티비티를 수행할 수 있다. 약한 컬렉션에서 제거된 객체들의 엔트리를 삭제한다. (레퍼런스 큐는 BlockingQueue와 같은 큐 해제 모드를 제공한다.)

WeakHashMap은 대부분의 Map 연산 중에 호출되는 expungeStaleEntries()이라고 하는 프라이빗 메소드를 갖고 있다. 이것은 종료된 레퍼런스용 레퍼런스 큐를 폴링하고 관련 매핑을 제거한다. Listing 7에 expungeStaleEntries()의 구현이 나와있다. 키-값 매핑을 저장하는데 사용되는 Entry 유형은 WeakReference를 확장하여 expungeStaleEntries()가 그 다음의 종료된 약한 참조를 요청할 때Entry를 가져온다. 콘텐트를 주기적으로 트롤링 하는 대신 Map을 청소하기 위해 레퍼런스 큐를 사용하는 것이 더 효율적이다. 살아있는 엔트리들이 클린업 프로세스에서 절대 건드리지 않기 때문이다. 실제로 인큐(enqueued) 레퍼런스가 있을 때만 수행된다.


Listing 7. WeakHashMap.expungeStaleEntries() 구현
    private void expungeStaleEntries() {
 Entry<K,V> e;
        while ( (e = (Entry<K,V>) queue.poll()) != null) {
            int hash = e.hash;

            Entry<K,V> prev = getChain(hash);
            Entry<K,V> cur = prev;
            while (cur != null) {
                Entry<K,V> next = cur.next;
                if (cur == e) {
                    if (prev == e)
                        setChain(hash, next);
                    else
                        prev.next = next;
                    break;
                }
                prev = cur;
                cur = next;
            }
        }
    }

결론

약한 참조와 약한 컬렉션은 힙 관리에 있어 강력한 툴이다. 애플리케이션이 일반 참조의 "모 아니면 도" 식의 접근이 아닌 보다 세련된 개념의 접근 방식을 사용할 수 있기 때문이다. 다음 달에는 소프트 레퍼런스(soft references)를 연구해 보자.


참고자료

교육

제품 및 기술 얻기

토론

신고

'개발/활용정보 > Java' 카테고리의 다른 글

Creating a Custom Event  (0) 2011.04.19
eclipse 단축키  (0) 2011.04.13
자바가 사용하는 메모리의 종류와 특징  (0) 2011.04.13
Java 메모리 설정 MaxPermSize  (0) 2011.04.13
Jboss Clustering  (0) 2011.04.13
Java theory and practice: 메모리 누수와 약한 참조  (0) 2011.04.13
Trackback 0 And Comment 0
prev | 1 | ··· | 78 | 79 | 80 | 81 | next

티스토리 툴바