finalize() not guaranteed to be called -- ever

P

Paul J. Lucas

Chris Smith said:
Although programmer-safety is nice, it isn't the main goal behind Java's
memory model. The more important concern is security-safety.

[ snip ]

I don't see what that's got to do with GC or Java's memory
model.
Here's the problem, though. First, what classes have destructors?
Let's say there are a hundred of them. Next, what kinds of references
can refer to them? Any reference to a superclass or superinterface
MIGHT refer to a class with a destructor.

Fine. Rather than have a special kind of reference, have a
special kind of class:

public counted class MyClass {
// ...
}

Then the JVM knows exactly which classes are reference counted.
Any class derived from a counted class is also counted.

- Paul
 
P

Paul J. Lucas

Chris Smith said:
Cleaner code looks like this:

FileInputStream in = new FileInputStream(inFile);

try
{
FileOutputStream out = new FileOutputStream(outFile);

try
{
// copy in to out
}
finally
{
out.close();
}
}
finally
{
in.close();
}

It doesn't look cleaner to me.
... and more importantly, it compiles.

Yes, well, I should have declared in and out outside the try.
Point taken, though. The code is definitely much simpler in C++ ...

Thank you.
... because of the existence of stack-allocated data structures (sometimes
called "objects") with destructors.

They're still objects:

class A {
public:
virtual void f();
};

class B : public A {
public:
void f();
};

void foo() {
B b; // stack allocated
A &a = b;
a.f(); // calls B::f()
}

They also still do data hiding and inheritance, two other
characteristics of objects.

- Paul
 
T

Thomas Hawtin

Paul said:
No, the function can return by *any* means and the destructors
*will* be called.

I don't mean by "remain silent" that it wont be called, but when it is
called and it does come across an exceptional condition it will fail to
report that.

Tom Hawtin
 
C

Chris Smith

Paul J. Lucas said:
Chris Smith said:
Although programmer-safety is nice, it isn't the main goal behind Java's
memory model. The more important concern is security-safety.

[ snip ]

I don't see what that's got to do with GC or Java's memory
model.

I suppose I wasn't clear in explaining it. Basically, Java security (in
environments where there is a SecurityManager, such as applets, EJBs,
many servlet deployments, app-specific plugin code, etc.) relies on
privileged Java code to check and prevent unauthorized operations. The
ability of unprivileged code to change random bits of memory on the heap
would obviously break that model. Unprivileged code, for example,
cannot be allowed to change the state of an instance of class
java.security.Policy!

If you allowed dangling pointers (that is, pointers to memory that has
since been deallocated), then you have no idea what you're allowing
unprivileged code to do. If that code is smart enough, it will
eventually find a way to modify something it shouldn't.

In practice, there are more subtle versions of this attack that would be
easier to pull off... for example, by mutating an instance of String you
could circumvent all manner of security checks. Obtaining a dangling
pointer to memory that is occupied by a String would be far easier than
doing the same for a Policy object. But the concept is the same in
either case. If Java thinks you've got a reference to a byte[], then
you could modify either data structure imperviously.

That's why it's absolutely critical that the user should never be able
to obtain a dangling pointer in Java. (At least without the use of JNI.
JNI screws up the whole security model anyway.)
Fine. Rather than have a special kind of reference, have a
special kind of class:

public counted class MyClass {
// ...
}

Then the JVM knows exactly which classes are reference counted.
Any class derived from a counted class is also counted.

That helps a little, perhaps... but all references to Object still need
to contain reference counting code. That cripples the Collections API
for everyone, whether they are reference-counting or not.

--
www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
D

Dale King

Paul said:
The thing you're missing is that in C++ you can use things like
auto_ptr<T> or create something more sophisticated. In Java,
you pretty much turn over everything to the GC and hope for the
best.

How am I missing that when I mentioned it several times in the part of
my post that you snipped. If you look at the source code you will find a
delete statement that manually deletes what was being pointed at.

What you are missing is that the only reason that the auto_ptr works is
becuase the auto_ptr is itself a stack-based object. Without objects
that are in variables you cannot have your automatic freeing (and the
associated creashes from dangling pointers ;-)
And when will [hypothetical Java destructors] be called? Is it OK if they are
never called until the program shuts down? If you want it to be guaranteed to
be some time sooner than that you'll have to explain how the JVM can
guarantee that.


Why can't a bit of reference-counting be used in addition to
the mark-and-sweep algorithm for implementing GC? When the
number of strong references decrements to zero, the destructor
is called.

Because reference counting is very bad performance wise and has many
problems.
 
D

Dale King

Paul said:
But day-to-day programming doesn't need "pure OO" so nobody
*cares* that the above is true in C++. Not every object needs
polymorphism. An excellent example is a File class. It
represents a concrete thing: a file on a disk. You don't need
to subclass it. You don't need polymorphism from it.

I certainly wasn't arguing about the "pureness" of OO as Chris talked
about. Merely that the way this is done in C can lead to confusion.
Assuming that b is an instance of a class that sublcasses A in the
following 2 lines of code:

A a1 = b;
A &a2 = b;

These lines will have very different results that can confuse newbies.

I have actually in the past argued for Java to have something like stack
based objects, but in my proposal this was something distinct from
classes. I proposed using struct instead of class. These structs could
only be stored in variables, you never could get a reference to them and
they could not be subclassed. This is actually very close to the C#
feature of the same name.

I should also point out that C# actually has a way to handle this
automatic disposal based on scope without resorting to stack based
objects. It has an IDisposable interface which defines the Dispose
method. They then have a using block construct that looks like this:

using( expression )
{
... some code
}

or

using( ATypeThatImplementsIDisposable var = expression )
{
... some code
}


It guarantees that when you leave that block of code by whatever means
that Dispose will get called on the object. This is just syntactic sugar
for the try finally code.

See
<http://msdn.microsoft.com/library/d...ry/en-us/csspec/html/vclrfcsharpspec_8_13.asp>
for a more thorough explanation.
 
D

Dale King

Paul said:
It doesn't look cleaner to me.




Yes, well, I should have declared in and out outside the try.




Thank you.



They're still objects:

But the point is that you only get this because you have objects that
exist in variables and are on the stack and therefore the object's
lifetime is tied to scope. Java doesn't have this. You can't just say
that Java needs to add destructors. Destructors buy you nothing over
manually called methods without stack-based objects or some way to tie
lifetime to program scope.
 
D

Dale King

I think the real solution is to adopt a feature similar to the using
statement from C#. This is really only syntactic sugar for the
try-finally, but it is a much cleaner syntax particularly when you need
to use several resources. See

Your last paragraph pretty much sums it up. I would add that the
destructors would then become an exit hook (essentially), but would be
more object-oriented as they are tied to the object itself and one would
not have to manually register an exit hook. Try/finally clauses would
remain the best way to clean up after yourself.

And how is that in any way different from runFinalizersOnExit()?
 
D

Dale King

Paul said:
Not if you use auto_ptr<T>.

And once again auto_ptr only works because it is a stack-based object
thus doesn't apply to my qualification about heap-based objects.
Only if you returns pointers or references to them.

Which is not prevented, thus you have crashes and security concerns as
related elsewhere in this thread.
I've never found this to be a problem since the object is often
created in one place, but passed by reference to other places.
Those other places still have polymorphic use of the object
they're passed.

I didn't say it was a big problem, just that it was one of the reasons
that stack-based objects were not included. It doesn't come up much in
most C++ programs because most C++ programs are not very object oriented
;-).
That personally doesn't concern me. I want a language that's
useful for *me*, not new programmers.

Then you should feel at home in C++.
[finalize] is not guaranteed to ever occur, but some safety net is better
than no safety net.

I don't particulary care for Heisenberg programming.

Then please explain how you get anything else in this case.

Destructors when objects go out of scope or via auto_ptr<T>.

And how many different ways can I explain that objects in Java, just
like C++ objects that are created on the heap, do not have scope.
Objects cannot go out of scope because they never had any scope to begin
with. Therefore the first part is not possible.

Auto_ptr only works because it is an object that is not created on the
heap, but resides in a variable that does have scope. Since Java does
not have any such variables, it cannot have an auto_ptr that does the job.

Therefore you have failed to explain how you get anything else.

The best solution I know of is the one from C# that explained elsewhere
in this thread which is syntactic sugar for the try-finally but
satisifies your complaints against try-finally because it is less easily
forgotten, harder to get wrong and less tedious to write.
 
C

Chris Uppal

Dale said:
Because reference counting is very bad performance wise and has many
problems.

I believe that reference counting (in a sufficiently sophisticated
implementation) is feasible and may even bring overall performance benefits as
heap sizes grow and grow.

(Indeed the programming language I use has a bit of ref-counting mixed into its
memory management as a performance booster.)

What ref-counting cannot bring you is a safe, usable, implementation of the
semantics that Paul is asking for -- the problem of cycles is not solvable in a
way that simultaneously compatible with less-than-Godlike programmers and
"normal" Java references-to-objects.

-- chris
 
P

Paul J. Lucas

Dale King said:
But the point is that you only get this because you have objects that
exist in variables and are on the stack and therefore the object's
lifetime is tied to scope. Java doesn't have this. You can't just say
that Java needs to add destructors. Destructors buy you nothing over
manually called methods without stack-based objects or some way to tie
lifetime to program scope.

They could at least be *guaranteed* to be called before JVM
termination, something even finalizers aren't guaranteed to do
(and runFinalizersOnExit() is deprecated because it's broken --
well, *fix* it).

- Paul
 
P

Paul J. Lucas

Thomas Hawtin said:
Paul J. Lucas wrote:

I don't mean by "remain silent" that it wont be called, but when it is
called and it does come across an exceptional condition it will fail to
report that.

What?? Any exception is propagated up the call stack. If it's
not caught, the program will die immediately.

- Paul
 
P

Paul J. Lucas

Dale King said:
I certainly wasn't arguing about the "pureness" of OO as Chris talked
about. Merely that the way this is done in C can lead to confusion.
Assuming that b is an instance of a class that sublcasses A in the
following 2 lines of code:

A a1 = b;
A &a2 = b;

These lines will have very different results that can confuse newbies.

Newbie confusion isn't my concern and reduction of it shouldn't
be the guiding principle of a programming language.
I should also point out that C# actually has a way to handle this
automatic disposal based on scope without resorting to stack based
objects.

Why not just use stack-based objects?

- Paul
 
P

Paul J. Lucas

Chris Smith said:
If you allowed dangling pointers (that is, pointers to memory that has
since been deallocated), then you have no idea what you're allowing
unprivileged code to do.

I still don't see what that has to do with having stack-based
objects, destructors, or reference-counted objects.
That helps a little, perhaps... but all references to Object still need
to contain reference counting code.

Have a twin class for Object: CountedObject.

- Paul
 
P

Paul J. Lucas

Dale King said:
How am I missing that when I mentioned it several times in the part of
my post that you snipped. If you look at the source code you will find a
delete statement that manually deletes what was being pointed at.

It's the "manual" part I have a problem with. Use of
as it's name suggests said:
What you are missing is that the only reason that the auto_ptr works is
becuase the auto_ptr is itself a stack-based object.

No, I understand that fully well.
Because reference counting is very bad performance wise and has many
problems.

Oh, like stopping everything to GC doesn't have performance
problems?

I don't see how merely incrementing/decrementing counters leads
to "very bad performance problems."

- Paul
 
P

Paul J. Lucas

Chris Uppal said:
-- the problem of cycles is not solvable in a way that simultaneously
compatible with less-than-Godlike programmers and "normal" Java
references-to-objects.

GC isn't perfect, yet Java uses GC. Why is your apparent
criteria for reference-counting perfection? The point is that
it's useful in some situations. But, again, in Java, you're
stuck with GC for all situations, good or not.

- Paul
 
P

Paul J. Lucas

Dale King said:
Paul J. Lucas wrote:

Which is not prevented, thus you have crashes and security concerns as
related elsewhere in this thread.

I want a language that allows more flexibility over safety,
i.e., a powerful tool for good programmers, not a language that
protects sloppy programmers from themselves.

BTW: lots of Java programs still crash for lots of reasons.
Then you should feel at home in C++.

I do, but am often needed to program in Java.
And how many different ways can I explain that objects in Java, just
like C++ objects that are created on the heap, do not have scope.

And how many different ways can I explain that I want Java to
*have* objects created on the stack?

Or at least a special kind of reference that's equivalent to
auto_ptr<T>:

void foo() {
auto MyObject foo = new MyObject();
// ...
}

When foo goes out of scope, the referrent gets GC'd immediately
(unless it's still strongly reachable by some other means).

- Paul
 
T

Thomas Hawtin

Paul said:
What?? Any exception is propagated up the call stack. If it's
not caught, the program will die immediately.

You are familiar with C++?

When the destructor of a resource is invoked and there is an error
closing that resource, what happens?

Tom Hawtin
 
T

Thomas Hawtin

Paul said:
Oh, like stopping everything to GC doesn't have performance
problems?

You are taking the piss? GC will cost, say, a few percent. Reference
counting will bring the system to it's knees. Particularly if it has
multiple hardware threads.
I don't see how merely incrementing/decrementing counters leads
to "very bad performance problems."

Reference counting is bad enough for it not to be used in multithreaded
C++. The counters have to be atomic.

That is not just atomic incrementing and decrementing. Suppose thread A
reads a reference out of an object and then increments the counter.
Thread B comes along at the same time and nulls out the reference,
decrementing the counter, destructing if it becomes zero. We have a
race. A more complex and expensive scheme is required.

Tom Hawtin
 
D

Dale King

Paul said:
It's the "manual" part I have a problem with. Use of
auto_ptr<T>, as it's name suggests, does automatic deletion of
an object.

Once again all heap based objects in C++ require manually deleting them.
You have another mechanism to trigger that manual deletion based on the
scope of some other variable. So once again it is not the lack of
destructors that is the issue, but the lack of that automatic triggering
mechanism based on scope.
No, I understand that fully well.

So do you understand now why I say that just adding destructors (which
are really no different than finalizers) buys you nothing.
Oh, like stopping everything to GC doesn't have performance
problems?

I don't see how merely incrementing/decrementing counters leads
to "very bad performance problems."

I think Thomas answered this one pretty well.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top