The Coherence Extend C++ API contains a C++ object model. You should become familiar with this object model if you want to implement the Coherence API. This section contains the following information:
The following section contains general information for writing code which uses the object model.
This coherence namespace contains the following general purpose namespaces:
coherence::lang
—the essential classes that make up the object model
coherence::util
—utility code, including collections
coherence::net
—network and cache
coherence::stl
—C++ Standard Template Library integration
coherence::io
—serialization
Although each class is defined within its own header file, you can use namespace-wide header files to facilitate the inclusion of related classes. We recommend including, at a minimum, coherence/lang.ns
in code that uses this object model.
The coherence::lang::Object
class is the root of the class hierarchy. This class provides the common interface for abstractly working with Coherence class instances. Object is an instantiable class that provides default implementations for the following functions.
equals
hashCode
clone
(optional)
toStream
(that is, writing an Object
to an std::ostream
)
See coherence::lang::Object
in the C++ API for more information.
In addition to its public interface, the Object class provides several features used internally. Of these features, the reference counter is perhaps the most important. It provides automatic memory management for the object. This automatic management eliminates many of the problems associated with object reference validity and object deletion responsibility. This management reduces the potential of programming errors which may lead to memory leaks or corruption. This results in a stable platform for building complex systems.
The reference count, and other object "life-cycle" information, operates in an efficient and thread-safe manner by using lock-free atomic compare-and-set operations. This allows objects to be safely shared between threads without the risk of corrupting the count or of the object being unexpectedly deleted due to the action of another thread.
To track the number of references to a specific object, there must be a level of cooperation between pointer assignments and a memory manager (in this case the object). Essentially the memory manager must be informed each time a pointer is set to reference a managed object. Using regular C++ pointers, the task of informing the memory manager would be left up to the programmer as part of each pointer assignment. In addition to being quite burdensome, the effects of forgetting to inform the memory manager would lead to memory leaks or corruption. For this reason the task of informing the memory manager is removed from the application developer, and placed on the object model, though the use of smart pointers. Smart pointers offer a syntax similar to normal C++ pointers, but they do the bookkeeping automatically.
The Coherence C++ object model contains a variety of smart pointer types, the most prominent being:
View
—A smart pointer that can call only const
methods on the referenced object
Handle
—A smart pointer that can call both const
and non-const
methods on the referenced object.
Holder
—A special type of handle that enables you to reference an object as either const
or non-const
. The holder remembers how the object was initially assigned, and returns only a compatible form.
Other specialized smart pointers are described later in this section, but the View, Handle, and Holder smart pointers will be used most commonly.
Note:
In this documentation, the term handle (with a lowercase "h") refers to the various object model smart pointers. The term Handle (with an uppercase "H") refers to the specific Handle smart pointer.By convention each managed class will have these nested-types corresponding to these handles. For instance the managed coherence::lang::String
class defines String::Handle
, String::View
, String::Holder
.
Assignment of handles follows normal inheritance assignment rules. That is, a Handle may be assigned to a View, but a View may not be assigned to a Handle, just like a const
pointer cannot be assigned to a non-const
pointer.
When dereferencing a handle that references NULL, the system will throw a coherence::lang::NullPointerException
instead of triggering a traditional segmentation fault.
For example, this code would throw a NullPointerException
if hs
==
NULL
:
String::Handle hs = getStringFromElsewhere(); cout << "length is " << hs->length() << end1;
All managed objects are heap allocated. The reference count—not the stack—determines when an object can be deleted. To prevent against accidental stack-based allocations, all constructors are marked protected, and public factory methods are used to instantiate objects.
The factory method is named create and there is one create method for each constructor. The create method returns a Handle rather than a raw pointer. For example, the following code will create a new instance of a string:
String::Handle hs = String::create("hello world");
By comparison, these examples are incorrect and will not compile:
String str("hello world); String* ps = new String("hello world);
All objects within the model, including strings, are managed and extend from Object
. Instead of using char*
or std::string
, the object model uses its own managed coherence::lang::String
class. The String
class supports ASCII and the full Unicode BML character set.
String objects can easily be constructed from char*
or std::string
strings, as shown in these examples:
Example 2-1 Examples of Constructing String Objects
const char* pcstr = "hello world"; std:string stdstr(pcstr); String::Handle hs = String::create(pcstr); String::Handle hs2 = String::create(stdstr);
The managed string is a copy of the supplied string and contains no references or pointers to the original. You can convert back, from a managed String to any other string type, by using getCString()
method. This returns a pointer to the original const
char*
. Strings can also be created using the standard C++ <<
operator, when coupled with the COH_TO_STRING
macro.
To facilitate the use of quoted string literals, the String::Handle
and String::View
support auto-boxing from const
char*
, and const std::string
. This enables you to write the code shown in the prior samples as:
Auto-boxing is also available for other types. See coherence::lang::BoxHandle
for details.
Handles are type safe, in the following example, the compiler will not allow you to assign an Object::Handle
to a String::Handle
, because not all Objects are Strings.
Object::Handle ho = getObjectFromSomewhere(); String::Handel hs = ho; // will not compile
However, this example will compile, as all Strings are Objects.
Example 2-4 Type Safe Casting Examples
String::Handle hs = String::create("hello world"); Object::Handle ho = hs; // will compile
For situations in which you want to down-cast to a derived Object type, you must perform a dynamic cast using the C++ RTTI (runtime type information) check and ensure that the cast is valid. The Object model provides helper functions to ease the syntax.
cast<H>(o)
—attempt to transform the supplied handle o
to type H
, throwing an ClassCastException
on failure
instanceof<H>(o)
—test if a cast of o
to H
is allowable, returning true
for success, or false
for failure
These functions are similar to the standard C++ dynamic_cast<T>
, but do not require access to the raw pointer.
The following example shows how to down cast a Object::Handle
to a String::Handle
:
Example 2-5 Down Casting Examples
Object::Handle ho = getObjectFromSomewhere(); String::Handle hs = cast<String::Handle>(ho);
The cast<H>
function will throw a coherence::lang::ClassCastException
if the supplied object was not of the expected type. The instanceof<H>
function can be used to test if an Object is of a particular type without risking an exception being thrown. Such checks or generally only needed for places where the actual type is in doubt.
Example 2-6 Object Type Checking with the instanceof<H> Function
Object::Handle ho = getObjectFromSomewhere(); if (instanceof<String::Handle>(ho)) { String::Handle hs = cast<String::Handle>(ho); } else if (instanceof<Integer32::Handle>(ho)) { Integer32::Handle hn = cast<Integer32::Handle>(ho); } else { ... }
Managed arrays are provided by using the coherence::lang::Array<T>
template class. In addition to being managed and adding safe and automatic memory management, this class includes the overall length of the array, and bounds checked indexing.
You can index an array by using its Handle's subscript operator, as shown in this example:
Array<int32_t>::Handle harr = Array<int32_t>::create(10); int32_t nTotal = 0; for (size32_t i = 0, c = harr->length; i < c; ++i) { nTotal += harr[i]; }
The object model supports arrays of C++ primitives and managed Objects
. Arrays of derived Object
types are not supported, only arrays of Object
, casting must be employed to retrieve the derived handle type. Arrays of Objects are technically Array<MemberHolder<Object>
>
, and typedef
'd to ObjectArray
for easier readability.
The coherence::util*
namespace includes several collection classes and interfaces that may be useful in your application. These include:
coherence::util::Collection
—interface
coherence::util::List
—interface
coherence::util::Set
—interface
coherence::util::Queue
—interface
coherence::util::Map
—interface
coherence::util::LinkedList
—implementation
coherence::util::HashSet
—implementation
coherence::util::DualQueue
—implementation
coherence::util::HashSet
—implementation
coherence::util::SafeHashMap
—implementation
coherence::util::WeakHashMap
—implementation
coherence::util::IdentityHashMap
—implementation
These classes also appear as part of the Coherence Extend API.
Similar to ObjectArray
, Collections
contain Object::Holders
, allowing them to store any managed object instance type.
In the object model, exceptions are also managed objects. This enables you to hold onto caught exceptions as a local variable or data member without the risk of object slicing.
All Coherence exceptions are defined by using a throwable_spec
and derive from the coherence::lang::Exception
class, which derives from Object
. Managed exceptions are not explicitly thrown by using the standard C++ throw
statement, but rather by using a COH_THROW
macro. This macro will set stack information, and then call the exception's raise
method, which ultimately calls throw. The resulting thrown object may be caught an the corresponding exceptions View
type, or an inherited View
type. Additionally these managed exceptions may be caught as standard const
std::exception
classes. The following example shows a try/catch block with managed exceptions:
Example 2-9 A Try/Catch Block with Managed Exceptions
try { Object::Handle h = NULL; h->hashCode(); // trigger an exception } catch (NullPointerException::View e) { cerr << "caught" << e <<endl; COH_THROW(e); // rethrow }
Note:
This exception could also have been caught asException::View
or const std::exception&
.In C++ the information of how an object was declared (such as const) is not available from a pointer or reference to an object. For instance a pointer of type const Foo*
, only indicates that the user of that pointer cannot change the objects state. It does not indicate if the referenced object was actually declared const, and is guaranteed not to change. The object model adds a runtime immutability feature to allow the identification of objects which can no longer change state.
The Object
class maintains two reference counters: one for Handles and one for Views. If an object is referenced only from Views, then it is by definition immutable, as Views cannot change the state, and Handles cannot be obtained from Views. The isImmutable()
method (included in the Object
class) can test for this condition. The method is virtual, allowing subclasses to alter the definition of immutable. For example, String contains no non-const methods, and therefore has an isImmutable() method that always returns true.
Note that once immutable, an object cannot revert to being mutable. You cannot cast away const
-ness to turn a Handle into a View as this would violate the proved immutability.
Immutability is important with respect to caching. The Coherence NearCache
and ContinuouQueryCache
can take advantage of the immutability to determine if a direct reference of an object can be stored in the cache, or if a copy must be created. Additionally, knowing that an object cannot change allows safe multi-threaded interaction without synchronization.
Frequently there will be the need to integrate existing classes into the object model. A typical example would be the need to store a data-object into a Coherence cache, which only supports storage of managed objects. As it would not be reasonable to require that pre-existing classes be modified to extend from coherence::lang::Object
, the object model provides an adapter which will automatically convert a non-managed plain old C++ class instance into a managed class instance at runtime.
This is accomplished by using the coherence::lang::Managed<T>
template class. This template class extends from Object and from the supplied template parameter type T
, effectively producing a new class which is both an Object and a T
. The new class can be initialized from a T
, and converted back to a T
. The result is an easy to use, yet very powerful bridge between managed and non-managed code.
See the API doc for coherence::lang::Managed
for details and examples.
The following section provides information necessary to write new managed classes, that is, classes which extend from Object
. The creation of new managed classes is required when you are creating new EventListeners
, EntryProcessors
, or Filter
types. They are not required when you are working with existing C++ data objects or making use of the Coherence C++ API. See the previous section for details on integration non-managed classes into the object model.
Specification-based definitions, or "specs" enables you to quickly define managed classes in C++.
Specification-based definitions are helpful when you are writing your own implementation of managed objects.
There are various forms of specs used to create different class types:
class_spec
—standard instantiatable class definitions
cloneable_spec
—cloneable class definitions
abstract_spec
—non-instantiatable class definitions, with zero or more pure virtual methods
interface_spec
—for defining interfaces (pure virtual, multiply inheritable classes)
throwable_spec
—managed classes capable of being thrown as exceptions
Specs automatically define these features on the class being spec'd:
Handles, Views, Holders
static create()
methods which delegate to protected constructors
virtual clone()
method delegating to the copy constructor
virtual sizeOf()
method based on ::sizeof()
super typedef
for referencing the class from which the defined class derives
inheritance from coherence::lang::Object
, when no parent class is specified by using extends<>
To define a class using specs, the class publicly inherits from one of the above specs. Each of these specs are parametrized templates. The parameters are as follows:
The name of the class being defined.
The class to publicly inherit from, specified by using an extends<>
statement, defaults to extends<Object>
This element is not supplied in interface_spec
Except in the case of extends<Object>
, the parent class is not derived from virtually
A list of interfaces implemented by the class, specified by using an implements<>
statement
All interfaces are derived from using public virtual inheritance
Note that the extends<>
parameter is note used in defining interfaces.
Example 2-10 illustrates using interface_spec
to define a Comparable
interface:
Example 2-10 An Interface Defined by interface_spec
class Comparable : public interface_spec<Comparable> { public: virtual int32_t compareTo(Object::View v) const = 0; };
Example 2-11 illustrates using interface_spec
to define a derived interface Number
:
Example 2-11 A Derived Interface Defined by interface_spec
class Number : public interface_spec<Number, implements<Comparable> > { public: virtual int32_t getValue() const = 0; };
Next a cloneable_spec
is used to produce an implementation. This is illustrated in in Example 2-12.
Note:
To support the auto-generated create methods, instantiatable classes must declare thecoherence::lang::factory<>
template as a friend. By convention this is the first statement within the class body.Example 2-12 An Implementation Defined by cloneable_spec
class Integer : public cloneable_spec<Integer, extends<Object>, implements<Number> > { friend class factory<Integer>; protected: Integer(int32_t n) : super(), m_n(n) { } Integer(const Integer& that) : super(that), m_n(that.m_n) { } public: virtual int32_t getValue() const { return m_n; } virtual int32_t compareTo(Object::View v) const { return getValue() - cast<Integer::View>(v)->getValue(); } virtual void toStream(std::ostream& out) const { out << getValue(); } private: int32_t m_n; };
The class definition in Example 2-12 is the equivalent the non-spec based definitions in Example 2-13.
Example 2-13 Defining a Class Without the use of specs
class Integer : public virtual Object, public virtual Number { public: typedef TypedHandle<const Integer> View; // was auto-generated typedef TypedHandle<Integer> Handle; // was auto-generated typedef TypedHolder<Integer> Holder; // was auto-generated typedef super Object; // was auto-generated // was auto-generated static Integer::Handle create(const int32_t& n) { return new Integer(n); } protected: Integer(int32_t n) : super(), m_n(n) { } Integer(const Integer& that) : super(that), m_n(that.n) { } public: virtual int32_t getValue() const { return m_n; } virtual int32_t compareTo(Object::View v) const { return getValue() - cast<Integer::View>(v)->getValue(); } virtual void toStream(std::ostream& out) const { out << getValue(); } // was auto-generated virtual Object::Handle clone() const { return new Integer(*this); } // was auto-generated virtual size32_t sizeOf() const { return ::sizeof(Integer); } private: int32_t m_n; };
Example 2-14 illustrates using the spec'd class:
What do all these concepts have in common? They all identify the state of an object, and as such will generally have similar implementation concerns. Simply put all data members referenced in one of these methods, will likely need to be referenced in all of the methods. Conversely any data members which are not referenced by one, should likely not be referenced by any of these methods. Consider the simple case of a HashSet::Entry
, which contains the well known key and value data members. Certainly these are to be considered in the equals method, and would likely be tested for equality by using a call to their own equals method, rather than through reference equality. Now what if this Entry also contains as part of the implementation of the HashSet
a handle to the next Entry within the HashSet
's bucket, and perhaps also contains a handle back to the HashSet
itself. Should these be considered in equals as well? Likely not, it would seem reasonable that comparing two entries consisting of equal keys and values, from two maps should be considered equal. Following this line of thought the hashCode
method on Entry would completely ignore data members except for key and value, and the Entry's hashCode
would be computed using the results of its key and value hashCode, rather then using their identity hashCode
. that is, a deep equality check in equals implies a deep hash in hashCode
. Moving onto clone it can be seen that in cloning an Entry, we would not want to clone all its data member, but only the key and value. Obviously cloning the parent Map
as part of clone the Entry would make no sense, and a similar argument can be made for cloning the handle to the next Entry. This line of thinking can be extended to the isImmutable method, and to serialization as well. While it is certainly not hard and fast rule it is worth considering this approach when implementing any of these methods.
The object model includes managed threads, which allows for easy creation of platform independent, multi-threaded, applications. The threading abstraction includes support for creating, interrupting, and joining threads. Thread local storage is available from the coherence::lang::ThreadLocal
class. Thread dumps are also available for diagnostic and troubleshooting purposes. The managed threads are ultimately wrappers around the system's native thread type, such as POSIX or Windows Threads. This threading abstraction is used internally by Coherence, but is available for the application, if necessary.
Example 2-15 illustrates how to create a new Runnable
instance and spawn a thread:
Example 2-15 Creating a Runnable Instance and Spawning a Thread
class HelloRunner : public class_spec<HelloRunner, extends<Object>, implements<Runnable> > { friend class factory<HelloRunner>; protected: HelloRunner(int cReps) : super(), m_cReps(cReps) { } public: virtual void run() { for (int i = 0; i < m_Reps; ++i) { Thread::sleep(1000); std::cout << "hello world" << std::endl; } } protected: int m_cReps; }; ... Thread::Handle hThread = Thread::create(HelloRunner::create(10)); hThread->start(); hThread->join();
Refer to coherence::lang::Thread
and coherence::lang::Runnable
for more information.
The primary functional limitation of a reference counting scheme is automatic cleanup of cyclical object graphs. Consider the simple bi-directional relationship illustrated in Figure 2-1.
In this picture, both A and B have a reference count of one, which keeps them active. What they don't realize is that they are the only things keeping each other active, and that no external references to them exist. Reference counting alone in unable to handle these self sustaining graphs, and memory would be leaked.
The provided mechanism for dealing with graphs is weak references. A weak reference is one which will reference an object, but not prevent it from being deleted. As illustrated in Figure 2-2, the A->B->A issue could be resolved by changing it to the following.
Where A now has a weak reference to B. If B were to reach a point where it was only referenced weakly, it would clear all weak references to itself and then be deleted. In this simple example that would also trigger the deletion of A, as B had held the only reference to A.
Weak references allow for construction of more complicated structures then this. But it becomes necessary to adopt a convention for which references are weak and which are strong. Consider a tree illustrated in Figure 2-3. The tree consists of nodes A, B, C; and two external references to the tree X, and Y.
Figure 2-3 Weak and Strong References to a Tree
In this tree parent (A) use strong references to children (B, C), and children use weak references to their parent. With the picture as it is, reference Y could navigate the entire tree, starting at child B, and moving up to A, and then down to C. But what if reference X were to be reset to NULL? This would leave A only being weakly referenced and it would clear all weak references to itself, and be deleted. In deleting itself there would no longer be any references to C, which would also be deleted. At this point reference Y, without having taken any action would now refer to the situation illustrated in Figure 2-4.
Figure 2-4 Artifacts after Deleting the Weak References
This is not necessarily a problem, just a possibility which must be considered when using weak references. To work around this issue, the holder of Y would also likely need to maintain a reference to A to ensure the tree did not dissolve away unexpectedly.
See the Javadoc for coherence::lang::WeakReference
, WeakHandle
, and WeakView
for usage details.
As is typical in C++, referencing an object under construction can be dangerous. Specifically references to this are to be avoided within a constructor, as the object initialization has not yet completed. For managed objects, creating a handle to this from the constructor will in most cases cause the object to be destructed before it ever finishes being created. To address this, the object model includes support for virtual constructors. The virtual constructor onInit
is defined by Object
and can be overridden on derived classes. This method is called automatically by the object model just after construction completes, and just before the new object is returned from its static create method. Within the onInit
method it is safe to reference this, to call virtual functions, and to hand out references to the new object to other class instances. Any derived implementation of onInit must include a call to super::onInit()
to allow the parent class to also initialize itself.
In addition to the Handle and View smart pointers (discussed previously), the object model contains several other specialized variants that can be used. For the most part use of these specialized smart pointers is limited to writing new managed classes, and they will not show up in normal application code.
Table 2-1 Advanced Handle Types Supported by Coherence for C++
Type | Thread-safe? | View | Notes |
---|---|---|---|
No |
Conditional on T |
The implementation of Handle and View |
|
No |
Conditional on T |
Allows automatic creating of managed objects from primitive types. |
|
No |
May |
May act as a Handle or a View. Basic types stored in collections |
|
No |
Yes |
Ensures |
|
Yes |
No |
Does not prevent destruction of referring object. |
|
Yes |
Yes |
Does not prevent destruction of referring object. |
|
Yes |
No |
Transfers |
|
Yes |
Yes |
Thread-safe View. |
|
Yes |
May |
May act a thread-safe Handle or View. |
Although the object model includes a thread-safe reference count, this does not provide automatic thread safety for the state of derived classes. As is typical it is up to each individual class implementation to choose to provide for higher level of thread safety. Regardless of the presence or lack of higher level thread-safety, the reference count remains thread-safe.
Every Object
in the object model can be a point of synchronization and notification. To synchronize an object and acquire its internal monitor, use a COH_SYNCHRONIZED
macro code block, as shown in Example 2-16:
Example 2-16 A Sample COH_SYNCHRONIZED Macro Code Block
SomeClass::Handle h = getObjectFromSomewhere(); COH_SYNCHRONIZED (h) { // monitor of Object referenced by h has been acquired if (h->checkSomeState()) { h->actOnThatState(); } } // monitor is automatically released
The COH_SYNCHRONIZED
block performs the monitor acquisition and release. You can safely exit the block with return
, throw
, COH_THROW
, break
, continue
, and goto
statements.
The Object
class includes wait()
, wait(timed)
, notify()
, and notifyAll()
methods for notification purposes. To call these methods, the caller must have acquired the objects's monitor. Refer to coherence::lang::Object
for details.
Read-write locks are also provided see coherence::util::ThreadGate
for details.
The Handle, View, and Holder inner types defined on managed classes are not thread-safe. That is it is not safe without some form of synchronization to have multiple threads use the same handle if any of them may change the handle to reference another object. There is an important distinction here, we are discussing the thread-safety of the handle, not the object referenced by the handle.
This lack of thread-safety is an performance optimization and assumes that the vast majority of handles are stack allocated. Stack allocated handles, with very few provisos are by their very nature thread-safe. The provisos are that it is not a static or global variable, and that references to the handle are not shared with other threads.
There are then two types of code which must account for thread-safety of handles.
Managed class implementations. It should be assumed that any instance of a managed class may be shared by multiple threads. Though this may not be strictly true, if a handle to the object is supplied to code outside of your control (for instance put into a cache), there is no guarantee that the object will not made be visible to other threads.
Non-managed multi-threaded application code.
There are optimizations in place for the first case, namely the special thread-safe handle types:
coherence::lang::MemberHandle
<T>
—thread-safe version of T::Handle
coherence::lang::MemberView
<T>
—thread-safe version of T::View
coherence::lang::MemberHolder
<T>
—thread-safe version of T::Holder
coherence::lang::WeakHandle
<T>
—thread-safe weak handle to T
coherence::lang::WeakView
<T>
—thread-safe weak view to T
These handle types may be read and written from multiple thread without the need for additional synchronization. They are primarily intended for use as the data-members of other managed classes, and they make use of their parent's internal atomic state to provide thread-safety. When using these handle types it is recommended that they be read into a normal stack-based handle if they will be accessed more then once within a code block. This assignment to a normal stack-based handle is thread-safe, and once completed allows for essentially free dereferencing of the stack-based handle. Note that when initializing thread-safe handles a reference to the parent class must be supplied as the first parameter, this reference can be obtained by calling self()
on the parent object.
Example 2-17 illustrates a trivial example of such a usage:
Example 2-17 Thread-safe Handle
class Employee : public class_spec<Employee> { friend class factory<Employee>; protected: Employee(String::View vsName, int32_t nId) : super(), m_vsName(self(), vsName), m_nId(nId) { } public: String::View getName() const { return m_vsName; // read is automatically thread-safe } void setName(String::View vsName) { m_vsName = vsName; // write is automatically thread-safe } int32_t getId() const { return m_nId; } private: MemberView<String> m_vsName; const int32_t m_nId; };
The same basic technique can be applied to non-managed classes as well. This is made possible, by using the defined synchronization point as the "parent" for the thread-safe handles. With this approach you must ensure that the "parent" m_hMutex
Object outlives the thread-safe handle m_vsName
. This is easily accomplished by declaring them as data-members of the same class, and declaring the "parent" before the handle. This is illustrated in Example 2-18:
Example 2-18 Thread-safe Handle as a Non-Managed Class
class Employee { public: Employee(String::View vsName, int32_t nId) : m_hMutex(Object::create()), m_vsName(*m_hMutex, vsName), m_nId(nId) { } public: String::View getName() const { return m_vsName; } void setName(String::View vsName) { m_vsName = vsName; } int32_t getId() const { return m_nId; } private: const Object::Handle m_hMutex; MemberView<String> m_vsName; const int32_t m_nId; };
This section provides information which can aid in diagnosing issues in applications which make use of the object mode.
Thread dumps are available for diagnostic and troubleshooting purposes. These thread dumps also include the stack trace. You can generate a thread dump by performing a CTRL+BREAK
(Windows) or a CTRL+BACKSLASH
(UNIX). Example 2-19 illustrates a sample thread dump:
Example 2-19 Sample Thread Dump
Thread dump Oracle Coherence for C++ v3.4b397 (Pre-release) (Apple Mac OS X x86 debug) pid=0xf853; spanning 190ms "main" tid=0x101790 runnable: <native> at coherence::lang::Object::wait(long long) const at coherence::lang::Thread::dumpStacks(std::ostream&, long long) at main at start "coherence::util::logging::Logger" tid=0x127eb0 runnable: Daemon{State=DAEMON_RUNNING, Notification=false, StartTimeStamp=1216390067197, WaitTime=0, ThreadName=coherence::util::logging::Logger} at coherence::lang::Object::wait(long long) const at coherence::component::util::Daemon::onWait() at coherence::component::util::Daemon::run() at coherence::lang::Thread::run()
While the managed object model reference counting helps to prevent memory leaks they are still possible. The most common way in which they are triggered is through cyclical object graphs. The object model includes heap analysis support to help identify if leaks are occurring, by tracking the number of live objects in the system. Comparing this value over time provides a simple means of detecting if the object count is consistently increasing, and thereby likely leaking. Once a probable leak has been detected, the heap analyzer can help track it down as well, by provided statistics on what types of objects appeared to have leaked.
Coherence provides a pluggable coherence::lang::HeapAnalyzer
interface. The HeapAnalyzer
implementation can be specified by using the tangosol.coherence.heap.analyzer system
property. The property can be set to one of the following values:
none
—No heap analysis will be performed.
object
—The coherence::lang::ObjectCountHeapAnalyzer
will be used. It provides simple heap analysis based solely on the count of the number of live objects in the system. This is the default analyzer.
class
—The coherence::lang::ClassBasedHeapAnalyzer
will be used. It provides heap analysis at the class level, that is it tracks the number of live instances of each class, and the associated byte level usage.
custom
—Lets you define your own analysis routines. You specify the name of a class registered with the SystemClassLoader
.
Heap information is returned when you perform a CTRL+BREAK
(Windows) or CTRL+BACKSLASH
(UNIX). The following is an example of heap analysis information is returned by the class based analyzer:
Example 2-20 Data Returned by a Heap Analyzer
Space Count Class 44 B 1 coherence::lang::Integer32 70 B 1 coherence::lang::String 132 B 1 coherence::util::SafeHashMap::Entry Total: 246 B, 3 objects, 3 classes
The above example was the heap analysis delta resulting from the insertion of a new entry into a Map
.
For all that the object model does to prevent memory corruption, it will typically be used along side non-managed code which could cause corruption. To combat this, the object model includes memory corruption detection support. When enabled, the object model's memory allocator will pad the beginning and end of each object allocation by a configurable number of pad bytes. This padding is encoded with a pattern which can later be validated to ensure that the pad has not been touched. If memory corruption occurs, and hits one of the pads, subsequent validations will detect the corruption. Validation is performed when the object is destroyed.
The debug version of the Coherence C++ API has padding enabled by default, using a pad size of 2*(word size), on each side of an object allocation. In a 32-bit build, this adds 16 bytes per object. Increasing the size of the padding will increase the chances of corruption hitting a pad, and thus the chance of detecting corruption.
The size of the pad can be configured by using the tangosol.coherence.heap.padding system property, which can be set to the number of bytes for the pre/post pad. Setting this system property to a non-zero value will enable the feature, and is available even in release builds.
Example 2-21 illustrates the results from an instance of memory corruption detection: