Inside Terracotta (转)

2. Terracotta Eclipse Plugin

开发基于Terracotta的应用程序的最便捷的方法就是使用TerracottaEclipse Plugin。http://www.terracotta.org/web/display/docs/Eclipse+Plugin+Guide上有详细的安装说明。安装完毕后,通过Terracotta->Add Terracotta Nature可以给已有的工程增加Terracotta Nature。此外也可以通过File->New->Project->Java->Terracotta Projects->Terracotta DSO Project创建一个Terracotta工程。Terracotta工程创建完毕后,工程的根目录下会创建tc-config.xml、terracotta目录和一个Boot jar。tc-config.xml包含了所有Terracotta相关的配置信息,例如需要进行字节码加强的类、Lock相关的配置,共享对象的root、分布式方法的配置等。Terracotta目录用于保存Terracotta客户端的日志和统计信息等。Boot jar的作用会在稍后的章节里介绍。接下来通过两个例子介绍一下Terracotta Eclipse Plugin的使用。

2.1 wait/notify
设想某个线程A调用了某个对象obj的wait方法后被阻塞,接下来另一个线程B调用了obj的notify方法从而唤醒了线程A。这在单个JVM中是司空见惯的场景了。但是有没有设想过B线程可以在一个不同于线程A所在的JVM中调用obj的notify方法从而唤醒线程A呢?这在Terracotta的世界里也是司空见惯的场景。
首先建立一个Terracotta工程,然后创建以下两个普通的Java类:

Java代码
package tcinaction;

public class A {

public static final Object OBJECT = new Object();

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

System.out.println("A is waiting on OBJECT...");
synchronized(OBJECT) {
OBJECT.wait();
}
System.out.println("A was woken up");
}
}


package tcinaction;

public class B {

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

System.out.println("B is calling A.OBJECT.notify()...");
synchronized(A.OBJECT) {
A.OBJECT.notify();
}
}
}

package tcinaction;

public class A {

public static final Object OBJECT = new Object();

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

System.out.println("A is waiting on OBJECT...");
synchronized(OBJECT) {
OBJECT.wait();
}
System.out.println("A was woken up");
}
}


package tcinaction;

public class B {

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

System.out.println("B is calling A.OBJECT.notify()...");
synchronized(A.OBJECT) {
A.OBJECT.notify();
}
}
}
然后在Package Explorer中选中A,单击右键选中Terracotta->Module A.java->Instrumented,也就是令Terracotta对A类进行字节码加强。对B类也执行同样操作。接下来在A类的OBJECT静态成员变量上单击右键,选中Terracotta->Field OBJECT->Shared root,这样A类的OBJECT就成了在Terracotta中共享的对象。再接下来在A类的main方法上单击右键,选中Terracotta->Method main->Autolock,在弹出的Specify Autolock Attributes对话框中选中Write。对B类的main方法也执行相同的操作。经过了以上操作之后,tc-config.xml的内容如下:

Xml代码
<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
<servers>
<server host="%i" name="localhost">
<dso-port>9510</dso-port>
<jmx-port>9520</jmx-port>
<data>terracotta/server-data</data>
<logs>terracotta/server-logs</logs>
<statistics>terracotta/cluster-statistics</statistics>
</server>
</servers>
<clients>
<logs>terracotta/client-logs</logs>
<statistics>terracotta/client-statistics/%D</statistics>
</clients>
<application>
<dso>
<instrumented-classes>
<include>
<class-expression>tcinaction.A</class-expression>
</include>
<include>
<class-expression>tcinaction.B</class-expression>
</include>
</instrumented-classes>
<roots>
<root>
<field-name>tcinaction.A.OBJECT</field-name>
</root>
</roots>
<locks>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.B.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.A.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
</dso>
</application>
</con:tc-config>

<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
<servers>
<server host="%i" name="localhost">
<dso-port>9510</dso-port>
<jmx-port>9520</jmx-port>
<data>terracotta/server-data</data>
<logs>terracotta/server-logs</logs>
<statistics>terracotta/cluster-statistics</statistics>
</server>
</servers>
<clients>
<logs>terracotta/client-logs</logs>
<statistics>terracotta/client-statistics/%D</statistics>
</clients>
<application>
<dso>
<instrumented-classes>
<include>
<class-expression>tcinaction.A</class-expression>
</include>
<include>
<class-expression>tcinaction.B</class-expression>
</include>
</instrumented-classes>
<roots>
<root>
<field-name>tcinaction.A.OBJECT</field-name>
</root>
</roots>
<locks>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.B.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.A.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
</dso>
</application>
</con:tc-config>

最后我们来验证一下程序的运行结果。首先选中A类,单击右键Run As->Terracotta DSO Application。此时首先会启动Terracotta server,然后会启动另外一个JVM,运行A类的main方法。输出如下:
A is waiting on OBJECT...
接下来选中B类,单击右键Run As->Terracotta DSO Application。此时会再启动一个JVM,执行B类的main方法,相关控制台的输出如下:
B is calling A.OBJECT.notify()...
回到A的控制台,发现其主线程已被唤醒并执行完毕,输出如下:
A is waiting on OBJECT...
A was woken up

2.2 Simple Messaging
在这个例子中介绍一个通过LinkedBlockingQueue在不同JVM中传递数据的方法。首先创建以下两个类:

Java代码
package tcinaction;

import java.util.concurrent.LinkedBlockingQueue;

public class MessageProducer {

public static final LinkedBlockingQueue<String> PIPE = new LinkedBlockingQueue<String>();

public static void main(String args[]) throws InterruptedException {
for(int i = 0; i < 100; i++) {
PIPE.offer("message-" + i);
Thread.sleep(1000);
}
}
}


package tcinaction;

public class MessageConsumer {

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

while(true) {
String message = MessageProducer.PIPE.take();
System.out.println(message);
}
}
}

package tcinaction;

import java.util.concurrent.LinkedBlockingQueue;

public class MessageProducer {

public static final LinkedBlockingQueue<String> PIPE = new LinkedBlockingQueue<String>();

public static void main(String args[]) throws InterruptedException {
for(int i = 0; i < 100; i++) {
PIPE.offer("message-" + i);
Thread.sleep(1000);
}
}
}


package tcinaction;

public class MessageConsumer {

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

while(true) {
String message = MessageProducer.PIPE.take();
System.out.println(message);
}
}
}
然后将MessageProducer 和 MessageConsumer配置为instrumented;各自的main方法配置为Autolock(Write);MessageProducer的PIPE静态成员变量配置为Shared root。此时tc-config.xml的内容如下:

Xml代码
<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
<servers>
<server host="%i" name="localhost">
<dso-port>9510</dso-port>
<jmx-port>9520</jmx-port>
<data>terracotta/server-data</data>
<logs>terracotta/server-logs</logs>
<statistics>terracotta/cluster-statistics</statistics>
</server>
</servers>
<clients>
<logs>terracotta/client-logs</logs>
<statistics>terracotta/client-statistics/%D</statistics>
</clients>
<application>
<dso>
<instrumented-classes>
<include>
<class-expression>tcinaction.MessageProducer</class-expression>
</include>
<include>
<class-expression>tcinaction.MessageConsumer</class-expression>
</include>
</instrumented-classes>
<locks>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.MessageConsumer.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.MessageProducer.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<roots>
<root>
<field-name>tcinaction.MessageProducer.PIPE</field-name>
</root>
</roots>
</dso>
</application>
</con:tc-config>

<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
<servers>
<server host="%i" name="localhost">
<dso-port>9510</dso-port>
<jmx-port>9520</jmx-port>
<data>terracotta/server-data</data>
<logs>terracotta/server-logs</logs>
<statistics>terracotta/cluster-statistics</statistics>
</server>
</servers>
<clients>
<logs>terracotta/client-logs</logs>
<statistics>terracotta/client-statistics/%D</statistics>
</clients>
<application>
<dso>
<instrumented-classes>
<include>
<class-expression>tcinaction.MessageProducer</class-expression>
</include>
<include>
<class-expression>tcinaction.MessageConsumer</class-expression>
</include>
</instrumented-classes>
<locks>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.MessageConsumer.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.MessageProducer.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<roots>
<root>
<field-name>tcinaction.MessageProducer.PIPE</field-name>
</root>
</roots>
</dso>
</application>
</con:tc-config>

最后我们来验证一下程序的运行结果。首先启动MessageConsumer,单击右键Run As->Terracotta DSO Application。然后启动MessageProducer。MessageConsumer的控制台上会显示出接收到的消息:
message-0
message-1
message-2



3 Inside Terracotta
3.1 Core Terracotta Concepts
3.1.1 Root
共享对象图中的顶层对象被称为root,它在Terracotta的配置文件中指定。所有经root引用可达的对象都会被Terracotta分配一个集群内唯一的object id,并在集群内共享直到被分布式垃圾收集器回收。需要注意的是,声明root对象的类也会被Terracotta隐含地标记为instrumented。
当集群内的某个JVM第一次对root引用赋值的时候,Terracotta会在集群内创建root,并且所有接下来的对root引用的赋值操作均会被Terracotta忽略(除非root是Terracotta中的literal values)。无论被何种修饰符修饰,root对象的生命周期都超过了单个JVM的范畴。尽管Terracotta可以保证集群中的root引用只被赋值一次,但是root对象的内容是可以被修改的,这些修改会被同步到Terracotta server。此外也不能避免对其constructor的调用,例如:

Java代码
package tcinaction.sharedroot;

public class A {
public static final A ROOT = new A();

public A() {
System.out.println("in A.A(), this: " + this);
}

public static void main(String args[]) throws InterruptedException {
System.out.println("root: " + ROOT);
Thread.sleep(Long.MAX_VALUE);
}
}

package tcinaction.sharedroot;

public class A {
public static final A ROOT = new A();

public A() {
System.out.println("in A.A(), this: " + this);
}

public static void main(String args[]) throws InterruptedException {
System.out.println("root: " + ROOT);
Thread.sleep(Long.MAX_VALUE);
}
} tc-config.xml的内容如下:

Xml代码
<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
<servers>

</servers>
<clients>

</clients>
<application>
<dso>
<locks>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.sharedroot.A.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<instrumented-classes>
<include>
<class-expression>tcinaction.sharedroot.A</class-expression>
</include>
</instrumented-classes>
<roots>
<root>
<field-name>tcinaction.sharedroot.A.ROOT</field-name>
</root>
</roots>
</dso>
</application>
</con:tc-config>

<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
<servers>

</servers>
<clients>

</clients>
<application>
<dso>
<locks>
<autolock auto-synchronized="false">
<method-expression>void tcinaction.sharedroot.A.main(java.lang.String[])</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<instrumented-classes>
<include>
<class-expression>tcinaction.sharedroot.A</class-expression>
</include>
</instrumented-classes>
<roots>
<root>
<field-name>tcinaction.sharedroot.A.ROOT</field-name>
</root>
</roots>
</dso>
</application>
</con:tc-config> 以Terracotta DSO Application的方式运行A后,控制台的输出如下:
in A.A(), this: tcinaction.sharedroot.A@1308c5c
root: tcinaction.sharedroot.A@1308c5c
接下来再次以Terracotta DSO Application的方式运行A,控制台的输出如下:
in A.A(), this: tcinaction.sharedroot.A@6828d4
root: tcinaction.sharedroot.A@83914b
当第二次运行A的时候,A的构造函数会被调用,并打印出这个对象的堆地址是@6828d4,由于集群中的ROOT引用已经在第一运行A的时候被赋值过,Terracotta忽略了第二次对ROOT引用的赋值,所以在main方法中打印出的ROOT地址是@83914b。
为什么第二次运行A时打印出来的ROOT地址(@83914b)跟第一次运行A时打印出的ROOT地址(@1308c5c)不同?Terracotta通过proxy保证了集群范围内的对象唯一性,这是逻辑上的唯一性,也就是说@1308c5c和@83914b是逻辑上的同一个ROOT实例。



3.1.2 Transactions
Terracotta 事务是一系列对共享对象修改的原子集合,它的边界是集群锁(clustered lock)的获取和释放。当某个线程获取集群锁时事务开启,对共享对象的修改都被加入到该事务中。在释放集群锁之后,事务被提交。
所有对共享对象的修改都必须在Terracotta 事务的上下文中。这意味着必须首先获得集群锁,然后才能对共享对象进行修改。如果某个线程试图在Terracotta 事务之外(更严格地说,还需要在经过字节码加强后代码中)修改共享对象,那么会导致运行时异常。在某个线程A获取集群锁之前,Terracotta会保证集群中由这把锁界定的事务中对共享对象的修改都会对线程A可见(这类似于Java语言规范的happens before语义和内存可见性的保证)。

3.1.3 Locks
Locks在Terracotta中有两个职责:协调多线程的并发访问和定义Terracotta 事务边界。Terracotta要求: Java代码中所有对共享对象的写操作都必须被同步(可以使用synchronized关键字或者java.util.concurrent包中锁相关的工具类)。But why?Terracotta的理念是并发程序的设计是充满挑战的,需要尽可能地减少犯错的可能。有些并发问题在开发过程中无法发现,在测试过程中无法发现,却可能在你最重要的客户面前爆发。解决同样一个问题,时机的不同(比如是在开发阶段,或者是在production阶段)可能就决定了你老板的大拇指是向上还是向下。
根据配置,Terracotta会对应用程序的字节码进行加强,以便增加Cluster locking相关的行为。配置文件中对集群锁的配置是在方法级别上。需要注意的是,如果声明这些方法的类没有被标记为instrumented,那么这些方法不会有任何Cluster locking相关的行为。也就是说如果在没有标记为instrumented的类中修改共享对象(这不会导致运行时异常,原因是Terracotta无法对uninstrumented的类进行检查),那么这些修改不会被同步到Terracotta server,也就不会对集群中的其它成员可见,从而导致共享对象在集群成员中处于不一致的状态。


3.1.3.1 Autolock
Terracotta里最常见的集群锁由autolock配置。Terracotta会检查所有与autolock匹配的方法中的同步块,通过拦截MONITORENTER和MONITOREXIT字节码指令,以增加Cluster locking相关的行为。以下是个简单的例子:

Xml代码
<autolock auto-synchronized="false">
<method-expression>* tcinaction.locks.A.*(..)</method-expression>
<lock-level>write</lock-level>
</autolock>

<autolock auto-synchronized="false">
<method-expression>* tcinaction.locks.A.*(..)</method-expression>
<lock-level>write</lock-level>
</autolock> "method-expression" 指定了匹配的方法(通过AspectWerkz Pattern Selection Language)。以上例子中,表达式"* tcinaction.locks.A.*(..)" 匹配tcinaction.locks.A类中所有的方法:表达式开头的 "*" 匹配所有可能的返回值类型;表达式结尾的 "*" 匹配所有的方法名;"(..)" 匹配所有可能的方法参数(包括没有参数)。autolock 还有一个auto-synchronize属性,如果为true,那么等效于该方法被synchronized关键字修饰。该属性通常用于已有的程序中修改共享对象的代码没有被同步,却无法直接重构代码的情况下。

3.1.3.2 Named Lock
除了autolock之外,Terracotta还支持named lock。跟autolock类似,"method-expression" 指定了匹配的方法;"lock-name"指定了命名锁的名字。当某个线程试图执行这些方法时,会从Terracotta server获得一个命名锁,这是一种非常粗粒度的集群锁,可能严重地影响应用性能,因此应该谨慎地使用。以下是个简单的例子:

Xml代码
<named-lock>
<lock-name>lockOne</lock-name>
<method-expression>* tcinaction.locks.A.*(..)</method-expression>
<lock-level>write</lock-level>
</named-lock>

<named-lock>
<lock-name>lockOne</lock-name>
<method-expression>* tcinaction.locks.A.*(..)</method-expression>
<lock-level>write</lock-level>
</named-lock>
3.1.3.3 Lock Level
所有与lock相关的配置还有一个属性lock level。Terracotta提供了以下四种可选级别:

Write。跟Java中的互斥锁类似,write locks保证任何时刻,集群中最多只有一个线程能够获得该锁。
Read。多个线程可以同时获得该锁,但是在持有该锁的时候不能修改共享对象(会导致运行时异常)。持有该锁的时候不能升级为Write locks,这也会导致运行时异常。当某个线程持有该锁的时候,在相同共享对象上试图获得Write locks的线程会被阻塞;当某个线程持有Write locks的时候,在相同共享对象上试图获得Read locks的线程也会被阻塞。在读取共享对象的时候,虽然Terracotta并不强制要求必须首先获得Read locks,但是在不持有锁的情况下试图读取共享对象可能会导致脏读,强烈建议不要这样做。
Synchronous-Write。该锁在Write locks的基础上进一步保证持有该锁的线程直到所有的修改已经被同步到Terracotta server,并得到Terracotta server的应答之后才会释放该锁。
Concurrent。该锁只是定义了事务边界。通常该锁只是被Terracotta libraries使用,并不建议在应用程序中使用。
3.1.4 Portability
3.1.1节曾经介绍过,在不同的JVM中打印同一个root的堆地址是不同的,因此对于那些依赖于Object.hashCode的实现(即没有改写该方法)的共享对象来说,在不同的JVM中得到的hash code是不同的。假如集群中有个HashMap<Object, Object>类型的共享对象,在两个JVM中以同一个Object对象作为key向该HashMap中存放数据时,会不会因为hashCode不同而导致保存到两个不同的entry中呢?在回答这个问题之前,首先介绍一下Terracotta中portability相关的概念。
可以被Terracotta共享的对象被称为"portable",与之对应的类必须被标记为instrumented。绝大多数被标记为instrumented的类的实例都是portable,但是有一小部分对象由于包含了特定平台或JVM相关的信息,因此不是portable(例如java.io.FileDescriptor、Thread和Runtime等)。同样,继承自non-portable类的实例也不是portable。
对于绝大多数的对共享对象,Terracotta可以跟踪对其的修改,并将修改后的新值同步到Terracotta server,这些共享对象被称为Physically Managed Object。然而有一些对象不是通过这种方式共享的:当共享对象被修改时,Terracotta会记录修改共享对象时所调用的方法签名和方法参数,然后在集群中的其它JVM上再次调用该方法(这其实是分布式方法调用,会在稍后介绍),这些调用也被称为"logic action"。通过这种方式被共享的对象被称为是Logically Managed Objects。通常有两种原因导致对象被logically managed:

性能。
共享对象包含了特定JVM相关的信息(例如java.util.Hashtable、java.util.HashMap和java.util.HashSet都是logically managed)。
至此,本节开头提出的问题的答案也应该明了了。
尽管logically managed objects是portable,但是由于Terracotta实现细节的原因,logically managed classes有以下限制:如果某个类继承自logically managed classes,并且声明了额外的成员变量,假如符合以下条件,那么这个类就不是portable:

直接写入从父类中继承的成员变量。
改写了从父类继承的方法。
当某个对象被集群共享的时候(例如被赋值到某个root引用),Terracotta首先会遍历通过该对象引用可达的整个对象图,同时检查对象图中的每个对象(可以配置成忽略transient对象)的portability,如果非portable则抛出运行时异常。此外Terracotta也会对logic action的方法参数进行portability检查。



3.1.5 Boot Jars
Terracotta会在应用程序的字节码中织入集群相关的代码,通常的时机是在类载入的时候。可以配置成对所有载入的类都进行字节码加强,这是最简单和最安全的方法。随着你对Terracotta理解的深入,你会越来越清楚究竟那些类需要进行字节码加强,从而在配置文件中指定(通过AspectWerkz Class Selection Expression)需要进行字节码加强的类,这样会加快类载入的速度,以及提高运行时的性能。
对于绝大多数类,Terracotta可以在载入时进行字节码加强,然而有些类(如rt.jar中的类)是通过boot classloader(是非Java实现的classloader)载入的,Terracotta无法对这些类在载入时进行字节码加强。因此Terracotta提供了Boot JAR Tool,它可以选择性地对这些类进行预处理,生成一个boot jar并保存到boot classpath的优先位置中。需要注意的是,由boot classloader载入的类无法被共享,除非它被包含在boot jar中;同样,继承自由boot classloader载入的类也无法被共享,除非它们的父类被包含在boot jar中。



3.1.6 Transience
与Java序列化机制中的transient关键字类似,Terracotta提供了transience机制以避免共享对象中的部分成员变量被集群共享。此外Terracotta还提供了初始化transient成员变量的机制。以下是其配置的例子:

Xml代码
<transient-fields>
<field-name>tcinaction.sharedroot.A.logger</field-name>
</transient-fields>

<transient-fields>
<field-name>tcinaction.sharedroot.A.logger</field-name>
</transient-fields> 以上的例子中,A的logger成员变量被标记为transient。
此外也可以使用Java的transient关键字修饰logger。在默认情况下,Terracotta在共享某个对象的时候不会忽略该对象中被transient修饰的成员变量。如果希望Terracotta对其进行忽略,那么需要在include节点内增加honor-transient,例如:

Xml代码
<instrumented-classes>
<include>
<honor-transient>true</honor-transient>
<class-expression>tcinaction.sharedroot.A</class-expression>
</include>
</instrumented-classes>

<instrumented-classes>
<include>
<honor-transient>true</honor-transient>
<class-expression>tcinaction.sharedroot.A</class-expression>
</include>
</instrumented-classes> 当包含transient成员变量的共享对象在集群中的某个JVM中被构造的时候,可以在配置文件中指定on-load,以便对transient成员变量进行初始化(如果无on-load配置,那么transient成员变量的值会是null或0)。on-load的配置有两种方式:

指定需要执行的方法名。
指定需要执行的BeanShell脚本。
3.1.7 Distributed Method Invocation
在Terracotta配置文件中,共享对象的任何方法都可以表标记为distributed,这意味着在集群的某个JVM中对该方法的调用,都会在集群的其它JVM中以相同的参数调用。DMI通常被用来作为分布式listener的实现。笔者建议,不要在分布式方法内修改共享对象的非transient成员变量。以下是其配置的例子:

Xml代码
<distributed-methods>
<method-expression>void tcinaction.locks.A.onIdChanged()</method-expression>
</distributed-methods>

<distributed-methods>
<method-expression>void tcinaction.locks.A.onIdChanged()</method-expression>
</distributed-methods>
3.1.8 Distributed Garbage Collection
Terracotta virtual heap类似于操作系统的虚拟内存,它允许集群中的JVM访问超过其本地堆最大容量的共享对象。也就是说集群的各个JVM的本地堆中并不一定包含所有的共享对象。当某个JVM需要访问某个共享对象时,如果该对象并不存在于该JVM的本地堆中,那么会从Terracotta server进行lazy load。
与之相反,Virtual Memory Manager(VMM)会将最少使用的共享对象的引用设置为null,从而在本地堆中清除。假如共享对象A包含了对共享对象B和C的引用,如果C并不经常被访问,那么VMM可能将C的引用设置为null,从而A被VMM部分清除(C被清除,B仍然被保留)。需要注意的是,对于大部分的Logically managed objects,VMM不能进行部分清除。目前VMM可以进行部分清除的Logically managed objects有ConcurrentHashMap、HashMap、 Hashtable、LinkedHashMap和Java arrays。
Distributed Garbage Collector(DGC)由Terracotta server执行,它的职责是从共享对象中标记不再被使用的共享对象,以便从Terracotta server的内存和持久存储中安全地删除。当共享对象不被任何root经引用可达,并且不存在于任何客户端的本地堆中时(出于各种原因,Terracotta server知道加入到集群的各个JVM的本地堆中包含哪些共享对象),它就可以被DGC回收(root对象不会被DGC回收)。



3.2 Terracotta Cluster
多个Terracotta server可以组成一个Terracotta server array。其中的每一个Terracotta server都履行以下两个职责:

集群内的并发管理和锁管理。Terracotta server跟踪各个JVM内哪些线程持有集群范围内的锁;哪些线程调用了共享对象的wait方法,以便在该共享对象的notify和notifyAll方法被调用后,可以正确地唤醒等待的线程。
共享对象的管理和持久化保存。Terracotta server跟踪各个Terracotta client中的共享对象,以及对其的修改,并根据配置以决定是否将修改进行持久化保存,同时通知其它需要访问这些共享对象的Terracotta client。
如果某个Terracotta server无法正常工作,那么相应的后果由以下因素决定:

Terracotta server是否工作在持久化模式下。
Standby Terracotta server是否被配置,以及是否在等待接管active server。
该Terracotta server是否是Terracotta server array中的一员。
如果该Terracotta server是Terracotta server array中的一员,那么集群可以继续正常工作。如果不是Terracotta server array中的一员,并且是唯一的active server,那么集群的行为如下:

Restarted Server's Mode Standby Server Existing Data Existing Clients Allowed Reconnect? New Clients Allowed Reconnect?
Non-persistent Not allowed Lost No No
Persistent None set up Saved Yes No
Persistent Yes - becomes active Saved Yes No



如果集群中某个Terracotta client无法正常工作,那么Terracotta server会把它从集群中移除,回收该client持有的锁,并且拒绝该client的重连请求(因为该client可能出于某种中间状态)。然而可以为这个client配置一个重连窗口,以便处理网络连接短暂失效的情况。当重连窗口打开的时候,之前连接过的client被允许重新连接。当某个client没有和Terracotta server建立连接之前,该client内所有试图获得锁的操作均被阻塞。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值