For example:
class Test{
Object foo =this;
}
If I instantiate Test, the reference count of any Test object will never
become zero (as it always keeps a reference to itself) so long as the foo
variable is never re-assigned.
Does this present a problem for Java's garbage collection?
Thanks,
Novice
Garbage collection in Java does not use reference counting. Rather, most
simple gc systems use a mark-and-sweep method.
-- Adam Maass
Is there somewhere I can find this documented?
The impression I get from the very high level documents I've read on
java.sun.com's website is:
"The Java runtime environment has a garbage collector that periodically
frees the memory used by objects that are no longer referenced."
So if an object references itself and never stops referencing itself that
object will always have at least one reference.
Thanks,
Novice
on the sun website and it says that:
"Circular strong references don't necessarily cause memory leaks."
This statement doesn't seem to indicate that all objects which are no longer
referenced (except by themselves) will be removed/deleted.
In fact, it goes on further to say:
"although the VM might not actually collect these objects for an indefinite
amount of time"
Which to me says that the VM may not remove/delete these objects from memory
until the VM is shut-down.
Is there any reason to believe otherwise?
Thanks,
Novice
The VM might not collect any object for an indefinite amount of time. So yes,
uncollected objects may remain in memory until the VM is shut down.
--
| Grant Wagner <gwa...@agricoreunited.com>
For example:
java.io.Writer
Novice
This to me is a problem - I mean server programs would eventually run out of
memory if they made use of classes with circular dependencies...
Can anyone provide any clarification on this?
Or do most people know to try to avoid circular dependencies?
Thanks,
Novice
OK, this isn't exactly crystal-clear from the specs, but here's the
definition of an object's lifecycle:
http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#74701
And here's the definition of the term "reachable," which is part of the
lifecycle definition:
http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#44762
The short answer is that an object that refers to itself may be garbage
collected if there are no references to it from any live thread.
Java garbage collection does not use (a naive implementation of)
reference-counting.
-- Adam Maass
The VM will always do garbage collection - and a complete garbage
collection if necessary - before reporting an out of memory condition.
The case of not having had anything collected until program shutdown
will only occur for programs that did not run out of memory during
execution.
>Can anyone provide any clarification on this?
>
>Or do most people know to try to avoid circular dependencies?
Circular dependencies aren't a problem. If you're using an
experimental VM, of course, you might want to make a point out of
checking its documentation on this point.
I believe some older VMs did conservative GC, which is a bit of a
mess.
Cheers
Bent D
--
Bent Dalager - b...@pvv.org - http://www.pvv.org/~bcd
powered by emacs
> | The VM might not collect any object for an indefinite amount of time.
> | So
> yes,
> | uncollected objects may remain in memory until the VM is shut down.
> |
> |
> This to me is a problem - I mean server programs would eventually run
> out of memory if they made use of classes with circular dependencies...
>
> Can anyone provide any clarification on this?
A full Garbage Collection will only run when the VM doesn't have enough
memory to create a new Object when required. A short-lived program or a
program that dives into a tight loop without creating and discarding many
objects may never trigger a full GC. This is why finalize() cannot be
relied on to clean up resources - the program may terminate normally
without the finalizers ever running. This does not indicate a definiency
in the GC implementation, merely the fact that GC is not needed.
> Or do most people know to try to avoid circular dependencies?
Only beginners try to avoid circular references. People who think or guess
that java uses reference counting.
Note that it may be worthwhile expicitly nulling a reference to an object
(or more important, a mesh of objects like a List) if you know you have
finished with it but that the object holding the reference is likely to
continue to exist for a long time to come. Oh, and beware of callbacks
such as event notifiers keeping references to otherwise discarded objects.
Steve
Novice> | The VM might not collect any object for an indefinite
Novice> amount of |time. So yes, uncollected objects may remain in
Novice> memory until the VM is shut |down.
Novice> |
Novice> This to me is a problem - I mean server programs would
Novice> eventually run out of memory if they made use of classes
Novice> with circular dependencies...
Novice> Can anyone provide any clarification on this?
Novice> Or do most people know to try to avoid circular
Novice> dependencies?
No. The JVM does not have to collect objects until it feels it wants
to. Maybe never.
However it is required to free all available memory before it throws
an out of memory error.
So, for instance, a JVM might choose not to GC until it runs short of
memory. This makes sense, as GC is expensive. Why do it if you don't
need to. If you are running out of memory, of course, this is a
different issue. Then you do need to.
In short, don't worry about circular dependencies. They will not cause
the GC problems. And generally don't worry about when objects that are
our of scope, are actually GC'd. Its an implementation issue, and it
will differ between JVM's.
Cheers
Phil
Not sure exactly what you mean by "available memory" here - I don't
believe it's actually *required* to free any memory whatsoever. It's
required to free as much memory as its garbage collector knows how to,
but a conforming JVM could (I believe) use short ints as references,
and only ever be able to allocate a total of 65535 objects, after which
it could throw an out of memory error. Not that that's exactly likely,
of course, but I *think* it would be able to conform to the spec.
--
Jon Skeet - <sk...@pobox.com>
http://www.pobox.com/~skeet/
If replying to the group, please do not mail me too
After it completes the marking, it then deletes the objects that
weren't marked, (since it has has a complete list of all the new'ed
objects), first calling finalize on the objects before removing the
memory.
Mark-and-sweep been in place since at least 1.1 (maybe before)..
"Adam Maass" <adam.nos...@comcast.net> wrote in message news:<J8icnVH_XYv...@comcast.com>...
>This to me is a problem - I mean server programs would eventually run out of
>memory if they made use of classes with circular dependencies...
You are imagining Java gc works by reference counting where circular
references are a problem. See
http://mindprod.com/jgloss/reference.html and
http://mindprod.com/jgloss/garbagecollection.html
for how Java's mark sweep or generational gc works.
--
Canadian Mind Products, Roedy Green.
Coaching, problem solving, economical contract programming.
See http://mindprod.com/jgloss/jgloss.html for The Java Glossary.
This is only true for certain environments.
The JVM specification declares that the behavior of the garbage
collector is not defined, and the JVM implementor has a free hand. The
only requirements are that there be a garbage collector, and that it
make its best effort to reclaim memory before throwing the
OutOfMemoryError.
Most of us are using Sun JVMs, and Sun has documented their GCs fairly
well. It's worth the trip to the Sun Web site to read the facts,
rather than relying on conjecture.
Modern Sun JVMs use multiple heaps, with separate garbage collectors
on each. These typically DO run on an as-needed basis, but in many
cases a "full GC" is never triggered even in a program that creates
and discards many objects.
On the other hand, if you're using RMI, the Distributed Garbage
Collector will force a "full GC" on a regular basis. By default, the
DGC triggers a full garbage collection every 60 seconds.
> This is why finalize() cannot be
> relied on to clean up resources - the program may terminate normally
> without the finalizers ever running.
Again, not *necessarily* true. It is true for the default situation,
but if you call System.runFinalizersOnExit(true) at some point, the
finalizers will always be executed. Heh... unless someone calls
System.runFinalizersOnExit(false) after you've called it with "true"
:-)
Historically, Sun JVMs have not used mark-and-sweep GCs.
The older Sun JVMs used mark-and-compact. The difference is that
mark-and-sweep leaves a checkerboarded heap.
Modern Sun JVMs (since 1.2.something) use multiple heaps, divided into
two generations for user objects and a third generation for system
objects. The two user generations use separate garbage collectors. The
young generation uses a single-pass "copying" garbage collector: all
live objects are copied from Eden and the current survivor space into
the new survivor space and possibly the tenured generation, and then
Eden and the current survivor space are declared empty, and the new
survivor space becomes the current survivor space. The tenured
generation uses mark-and-compact.
As of 1.4.1, Sun's JVMs have added some additional GC options, one of
which is a low-latency, mostly concurrent, mark-and-sweep GC for the
tenured generation. Its operation is a six-phase process that is a
*bit* more complicated than an ordinary mark-and-sweep. See the FAQ
reference below.
A couple of useful references:
http://java.sun.com/docs/hotspot/gc1.4.2/faq.html
http://java.sun.com/docs/hotspot/gc1.4.2/
why don't you stress-test this little class by generating trillions of
objects, and watch the GC-log
Ahh, this is/was part of my problem - when I started looking for information
on how Java's Garbage Collection works, I kept running into references to
this "HotSpot" thing.
I discarded them thinking that "HotSpot" wasn't implemented in Sun's
JVM's... Sun does put articles on its website that aren't necessarily in
their current JVM's.
I just looked up what "HotSpot" is and found that:
Hotspot is a reference to the vm; it has the ability to locate bottleneck
code and compile it to native inline code rather that interpreting the code.
The resulting code is a mix of comipled code and interpreted code, and the
mix can be tweaked somewhat by the vm options.
Thanks for the references,
Novice
>watch the GC-log
how would he to that?
> "Steve Horsley" <steve.h...@virgin.NO_SPAM.net> wrote:
>> This is why finalize() cannot be
>> relied on to clean up resources - the program may terminate normally
>> without the finalizers ever running.
>
> Again, not *necessarily* true. It is true for the default situation,
> but if you call System.runFinalizersOnExit(true) at some point, the
> finalizers will always be executed. Heh... unless someone calls
> System.runFinalizersOnExit(false) after you've called it with "true"
> :-)
runFinalizerdOnExit() is deprecated as "inherently unsafe".
Steve
by using java -verbose:gc
another way is to set the maximum amount of allocatable memory to a very low
value, for example 2MB, then create 4 million objects with references to
itself and wait for an OutOfMemoryError, although this is a quite dirty way
of testing ofcourse
>The older Sun JVMs used mark-and-compact. The difference is that
>mark-and-sweep leaves a checkerboarded heap.
IIRC we discussed this many years ago. At the time some JVM guru said
that Java's reference implementation allowed objects to move, but they
never actually moved the objects to compact RAM. So it was a true
mark and sweep, not mark and compact. How did you come by your
version what went on inside back then?
While I didn't expect the circular references to be a problem for the Sun
VM, I did wanted to test it just for the record.
I wrote a test case (A) like this:
<hr><code id="SelfReference.java">
public class SelfReference
{
Object reference;
SelfReference()
{
reference=this;
}
static void test()
{
for (int i=0;i<1000;i++) new SelfReference();
}
public static void main(String[] args)
{
test();
}
}
</code><hr>
I ran this through JProfiler (anybody want to fund me so I can buy a
license? I'm still doing backpayments on my 20' plasma tv).
The garbage collector on the Sun 1.4.1 does clear every reference. I would
expect this, since this shouldn't be too hard for a garbage collector to
detect as being unreachable and thus eligible for garbage
collection.
BUT try it like this:
<hr><code id="SelfReference.java">
public class SelfReference
{
Object reference;
SelfReference()
{
reference= new SelfReferent(reference);
}
static void test()
{
for (int i=0;i<1000;i++) new SelfReference();
}
public static void main(String[] args)
{
test();
}
}
class SelfReferent
{
Object refrerer;
SelfReferent(Object referent)
{
referer = referent;
}
}
</code><hr>
If I run this test, and call System.gc() manually on JProfile, there are
still 8 allocations left on the heap (240 bytes). While this doesn't
seem serious, it is a problem if such code is present in a long-running
server process. This is reproducible.
So I must say that self references aren't a problem, but circular
dependencies are.
Greets
Bhun.
What you are doing here is passing null to the constructor. This
shouldn't be producing circular references.
> }
> static void test()
> {
> for (int i=0;i<1000;i++) new SelfReference();
> }
> public static void main(String[] args)
> {
> test();
> }
>}
>
>class SelfReferent
>{
> Object refrerer;
> SelfReferent(Object referent)
> {
> referer = referent;
> }
>}
></code><hr>
>
>If I run this test, and call System.gc() manually on JProfile, there are
>still 8 allocations left on the heap (240 bytes). While this doesn't
Do you have reason to believe that JProfile's manual GC does a
complete GC?
Also, your program should be terminating long before you have any
chance of running a manual GC. Are you sure you're not just looking at
a static heap image representing the state your program was in as it
was about to terminate?
> In article <pan.2003.07.17...@to.kenderland.and.back.org>,
> dhek bhun kho <bh...@to.kenderland.and.back.org> wrote:
>><hr><code id="SelfReference.java">
>>public class SelfReference
>>{
>> Object reference;
>> SelfReference()
>> {
>> reference= new SelfReferent(reference);
>
> What you are doing here is passing null to the constructor. This
> shouldn't be producing circular references.
<noise>
Oops. Typo. Shoot me. Darn. Why aren't computer artificial fuzzy
intelligent yet?
</noise>
It should read:
<code>
reference = new SelfReferent(this);
</code>
> Do you have reason to believe that JProfile's manual GC does a
> complete GC?
Yes. The manual states it, according to the manual they use JVMPI to
interact with the JVM. Or I might have found a 'bug' in their software. I
tried sample sizes from 10-10000; and keep getting a few left over
objects. This does not happen if I use the non-circular dependent version.
What am I doing wrong?
> Also, your program should be terminating long before you have any
> chance of running a manual GC. Are you sure you're not just looking at
> a static heap image representing the state your program was in as it
> was about to terminate?
I don't think so. The profiler keeps the VM alive after the main method
exits.
If I do not call gc() manually (of which I do not know how it is really
invoked, would have to check JVMPI documentation) then _all_
allocations are still visible (after the main method has finished). This
happens with both versions of the minimal test code.
When I do call the gc() method, all allocations disappear for the self
referring version and for the circular dependent one a few allocations are
still there. When I wrap the object referred to by the new call within a
SoftReference object, no allocations remain after calling the garbage
collector through the profiler.
I must admit that this is not the most ideal and idealistic test at all.
Do you know by chance, how to 100% certain? TIA.
> Cheers
> Bent D
Greets
Bhun.
Use a free profiler instead or keep switching between optimizeit, jprobe and
jprofiler.
: class SelfReferent
: {
: Object refrerer;
: SelfReferent(Object referent)
: {
: referer = referent;
: }
: }
Ok, this code does not compile, refrerer is mispelled. So obviously this
is not the code you used.
: If I run this test, and call System.gc() manually on JProfile, there are
: still 8 allocations left on the heap (240 bytes). While this doesn't
: seem serious, it is a problem if such code is present in a long-running
: server process. This is reproducible.
If I fix your spelling error and run it with jdk/1.4.2 and my profiler, jmp,
I see that my jvm does not call GC before the test is over so I have
1000 instances of SelfReferent and 1000 instances of SelfReference.
After calling GC all of them dissapears. So my jvm does seem to handle this
case in a good way.
(to test this I added a System.in.read (); in main, after the call to test).
: So I must say that self references aren't a problem, but circular
: dependencies are.
Self references are not a problem, neither is circular references.
As someone already said, can you be sure that jprofiler-GC does a
full GC.
Also: when did you get the numbers? your test case exits very quickly so
had the GC been completed before your jvm was shut down?
/robo
I'm not familiar with JVMPI so I'll just assume they know what they're
talking about <g>
>What am I doing wrong?
Nothing that I can think of.
>> Also, your program should be terminating long before you have any
>> chance of running a manual GC. Are you sure you're not just looking at
>> a static heap image representing the state your program was in as it
>> was about to terminate?
>
>I don't think so. The profiler keeps the VM alive after the main method
>exits.
Does it do this in some mystical magical way or has it just fired up
its own non-daemon thread to keep it all ticking merrily on? If it's
magical, that might affect how things are collected (or not) I
suppose.
>If I do not call gc() manually (of which I do not know how it is really
>invoked, would have to check JVMPI documentation) then _all_
>allocations are still visible (after the main method has finished). This
>happens with both versions of the minimal test code.
>
>When I do call the gc() method, all allocations disappear for the self
>referring version and for the circular dependent one a few allocations are
>still there. When I wrap the object referred to by the new call within a
>SoftReference object, no allocations remain after calling the garbage
>collector through the profiler.
I'm out of ideas then.
>I must admit that this is not the most ideal and idealistic test at all.
>Do you know by chance, how to 100% certain? TIA.
I have generally accepted that garbage collection is (apparantly)
non-deterministic and that however much I try to threaten it or yell
at it, it will still collect in its own good time. I tend to use
OptimizeIt to track down memory leaks and find my efforts constantly
frustrated by the fact that even when I have correctly unregistered
all my listeners, diposed all my windows, etc., it will still take up
to several minutes for the GC to catch on - even after hitting the
"collect" button in the profiler.
I don't really know how to get around this. Try to let the thing sit
for a couple of hours before checking on it again I suppose :-)
Are you aware of any decent free ones?
>If I fix your spelling error and run it with jdk/1.4.2 and my profiler, jmp,
>I see that my jvm does not call GC before the test is over so I have
>1000 instances of SelfReferent and 1000 instances of SelfReference.
>After calling GC all of them dissapears. So my jvm does seem to handle this
>case in a good way.
Did you fix the "this"-bug in order to get circular references?
> In article <pan.2003.07.17....@to.kenderland.and.back.org>,
> dhek bhun kho <bh...@to.kenderland.and.back.org> wrote:
> I'm not familiar with JVMPI so I'll just assume they know what they're
> talking about <g>
<a href="http://java.sun.com/j2se/1.4.1/docs/guide/jvmpi/jvmpi.html">
It's the java layer of the debugging/profiling interface.</a> I would hope
that such a layer would be reliable to do profiling with. But then again
it's still experimental. I have no clue on the current development status
of the memory profiling code.
>>What am I doing wrong?
>
> Nothing that I can think of.
>
>>> Also, your program should be terminating long before you have any
>>> chance of running a manual GC. Are you sure you're not just looking at
>>> a static heap image representing the state your program was in as it
>>> was about to terminate?
>>
>>I don't think so. The profiler keeps the VM alive after the main method
>>exits.
>
> Does it do this in some mystical magical way or has it just fired up
> its own non-daemon thread to keep it all ticking merrily on? If it's
> magical, that might affect how things are collected (or not) I
> suppose.
Magical? :) You mean like casting a contingency spell to dimension door
every where with mirror images and stoneskin?
> at it, it will still collect in its own good time. I tend to use
> OptimizeIt to track down memory leaks and find my efforts constantly
> frustrated by the fact that even when I have correctly unregistered
> all my listeners, diposed all my windows, etc., it will still take up
> to several minutes for the GC to catch on - even after hitting the
> "collect" button in the profiler.
:) Makes profiling hard. Some people ask how they can obfuscate code, I
think the easiest way is to use a lot of indirection and patterns tied
together with a lot of native libraries. It's called Java.
> frustrated by the fact that even when I have correctly unregistered
> all my listeners, diposed all my windows, etc., it will still take up
> to several minutes for the GC to catch on - even after hitting the
> "collect" button in the profiler.
Well don't waste too much time on this. It's a very unreal test case, and
not worth the effort as if there is a leak and it's only 240 bytes in a
whole application, then it should be of no problem (as long as the leak is
just an one time happening).
What's even more unreal is that you would write a class where an instance
references itself. Does an object not always have a reference to itself?
Greets
Bhun.
I would _guess_ that there's still some leeway for the garbage
collector implementation to do whatever it bloody well pleases, but I
could be wrong of course.
>
>Magical? :) You mean like casting a contingency spell to dimension door
>every where with mirror images and stoneskin?
I can certainly see how that might confuse the garbage collector <g>
Well, unless it's Dilbert's garbage collector I suppose. He'd feel
right at home.
>
>:) Makes profiling hard. Some people ask how they can obfuscate code, I
>think the easiest way is to use a lot of indirection and patterns tied
>together with a lot of native libraries. It's called Java.
There's a _reason_ there is a refactoring called "remove an
abstraction layer" <g>
>What's even more unreal is that you would write a class where an instance
> references itself. Does an object not always have a reference to itself?
I don't think it would be too uncommon. At the very least, you could
get it in an object that holds references to other utility objects and
you have decided to implement these as part of the same class. That
is, you might have an API that goes something like
abstract class Foo
{
private FooPartner myPartner;
public void setPartner(FooPartner p) { myPartner = p; }
...
}
interface FooPartner
{
boolean beFriendly();
}
and then decide to make yourself a new Foo that does it all itself
class RealFoo extends Foo implements FooPartner
{
public RealFoo()
{
setPartner(this);
}
public boolean beFriendly()
{
System.out.println("Hi there pal");
return true;
20 feet? That's one damn big TV ;-)
Michiel
< snip >
> I ran this through JProfiler (anybody want to fund me so I can buy a
> license? I'm still doing backpayments on my 20' plasma tv).
Oh, yeah. You have my pity.
No, wait. That's envy. >8-)
< snip >
--
Tukla, Squeaker of Chew Toys
Official Mascot of Alt.Atheism
o/ Frank's two-thousand-inch TV.
Everybody come and see...
You won't believe it! /o
> Phillip Lord <p.l...@russet.org.uk> wrote:
>> No. The JVM does not have to collect objects until it feels it wants
>> to. Maybe never.
>>
>> However it is required to free all available memory before it throws
>> an out of memory error.
>
> Not sure exactly what you mean by "available memory" here - I don't
> believe it's actually *required* to free any memory whatsoever. It's
> required to free as much memory as its garbage collector knows how to,
> but a conforming JVM could (I believe) use short ints as references,
> and only ever be able to allocate a total of 65535 objects, after
> which it could throw an out of memory error. Not that that's exactly
> likely, of course, but I *think* it would be able to conform to the
> spec.
Fair enough. But Phillip's statement is precisely how I'd state what you
stated. I believe that saying "required to free all available memory"
/means/ "required to free as much as its gc knows how to".
However in your example, are you sure a GC's would be conformant to spec if
they were to allow only 65536 objects and never try to reuse references of
dead objects? That is, 65536 new objects, regardless of how many are freed?
Gads.
Right.
> However in your example, are you sure a GC's would be conformant to spec if
> they were to allow only 65536 objects and never try to reuse references of
> dead objects? That is, 65536 new objects, regardless of how many are freed?
> Gads.
I believe that would be a conformant JVM. Not a useful one, but
conformant.
Jon> Thomas G. Marshall
Jon> <tgm2tothe...@hotmail.replaceTextWithNumber.com> wrote:
>> Fair enough. But Phillip's statement is precisely how I'd state
>> what you stated. I believe that saying "required to free all
>> available memory" /means/ "required to free as much as its gc
>> knows how to".
Jon> Right.
From my dodgy memory of the spec, I believe that the spec defines when
memory can be freed, and when it can not. In other words conformant
JVM's will have the same definition of whether a object can be freed
or not.
So to "free all available memory" is a stronger statement than "to
free as much as its GC knows how to".
A JVM, for instance, which used strict reference counting would not be
conformant, because it could be made to run out of memory with
circular references. Hence it would run out before it had freed all
available memory, although it may well have freed as much as it's GC
knows how to.
This still leaves a lot of scope for different mechanisms for GC.
Phil
>>>>>> "Jon" == Jon Skeet <sk...@pobox.com> writes:
>
> Jon> Thomas G. Marshall
> Jon> <tgm2tothe...@hotmail.replaceTextWithNumber.com> wrote:
> >> Fair enough. But Phillip's statement is precisely how I'd state
> >> what you stated. I believe that saying "required to free all
> >> available memory" /means/ "required to free as much as its gc
> >> knows how to".
>
> Jon> Right.
>
> From my dodgy memory of the spec, I believe that the spec defines when
> memory can be freed, and when it can not. In other words conformant
> JVM's will have the same definition of whether a object can be freed
> or not.
>
> So to "free all available memory" is a stronger statement than "to
> free as much as its GC knows how to".
What an odd position I'm in.
OP says x. Follow up says !x. I agree with follow up but backup OP's x.
OP says !x. I then agree with follow up, but not OP.
OI, I just might be my own grandpa.
Hmm. From chapter 3:
<quote>
To implement the Java virtual machine correctly, you need only be able
to read the class file format and correctly perform the operations
specified therein. Implementation details that are not part of the Java
virtual machine's specification would unnecessarily constrain the
creativity of implementors. For example, the memory layout of run-time
data areas, the garbage-collection algorithm used, and any internal
optimization of the Java virtual machine instructions (for example,
translating them into machine code) are left to the discretion of the
implementor.
</quote>
On the other hand, from section 2.6:
<quote>
A class instance is explicitly created by a class instance creation
expression, or by invoking the newInstance method of class Class. An
array is explicitly created by an array creation expression. An object
is created in the heap and is garbage-collected after there are no more
references to it. Objects cannot be reclaimed or freed by explicit
language directives.
</quote>
> A JVM, for instance, which used strict reference counting would not be
> conformant, because it could be made to run out of memory with
> circular references. Hence it would run out before it had freed all
> available memory, although it may well have freed as much as it's GC
> knows how to.
>
> This still leaves a lot of scope for different mechanisms for GC.
I believe a lot of VMs *were* conservative in their garbage collection
early on though. Maybe none of them conformed to the spec - chances are
they didn't in other ways anyway...