finalize called on strongly reachable object in Java 8

0 votes

We recently upgraded our message processing application from Java 7 to Java 8. Since the upgrade, we get an occasional exception that a stream has been closed while it is being read from. Logging shows that the finalizer thread is calling finalize() on the object that holds the stream (which in turn closes the stream).

The basic outline of the code is as follows:

MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );

MIMEWriter and MIMEBodyPart are part of a home-grown MIME/HTTP library. MIMEBodyPart extends HTTPMessage, which has the following:

public void close() throws IOException
{
    if ( m_stream != null )
    {
        m_stream.close();
    }
}

protected void finalize()
{
    try
    {
        close();
    }
    catch ( final Exception ignored ) { }
}

The exception occurs in the invocation chain of MIMEWriter.writePart, which is as follows:

  1. MIMEWriter.writePart() writes the headers for the part, then calls part.writeBodyPartContent( this )
  2. MIMEBodyPart.writeBodyPartContent() calls our utility method IOUtil.copy( getContentStream(), out ) to stream the content to the output
  3. MIMEBodyPart.getContentStream() just returns the input stream passed into the contstructor (see code block above)
  4. IOUtil.copy has a loop that reads an 8K chunk from the input stream and writes it to the output stream until the input stream is empty.

The MIMEBodyPart.finalize() is called while IOUtil.copy is running, and it gets the following exception:

java.io.IOException: Stream closed
    at java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:67)
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:142)
    at java.io.FilterInputStream.read(FilterInputStream.java:107)
    at com.blah.util.IOUtil.copy(IOUtil.java:153)
    at com.blah.core.net.MIMEBodyPart.writeBodyPartContent(MIMEBodyPart.java:75)
    at com.blah.core.net.MIMEWriter.writePart(MIMEWriter.java:65)

We put some logging in the HTTPMessage.close() method that logged the stack trace of the caller and proved that it is definitely the finalizer thread that is invoking HTTPMessage.finalize() while IOUtil.copy() is running.

The MIMEBodyPart object is definitely reachable from the current thread's stack as this in the stack frame for MIMEBodyPart.writeBodyPartContent. I don't understand why the JVM would call finalize().

I tried extracting the relevant code and running it in a tight loop on my own machine, but I cannot reproduce the problem. We can reliably reproduce the problem with high load on one of our dev servers, but any attempts to create a smaller reproducible test case have failed. The code is compiled under Java 7 but executes under Java 8. If we switch back to Java 7 without recompiling, the problem does not occur.

As a workaround, I've rewritten the affected code using the Java Mail MIME library and the problem has gone away (presumably Java Mail doesn't use finalize()). However, I'm concerned that other finalize() methods in the application may be called incorrectly, or that Java is trying to garbage-collect objects that are still in use.

I know that current best practice recommends against using finalize() and I will probably revisit this home-grown library to remove the finalize() methods. That being said, has anyone come across this issue before? Does anyone have any ideas as to the cause?

Mar 5, 2019 in Java by Sushmita
• 6,920 points
824 views

1 answer to this question.

0 votes

A bit of conjecture here. It is possible for an object to be finalized and garbage collected even if there are references to it in local variables on the stack, and even if there is an active call to an instance method of that object on the stack! The requirement is that the object be unreachable. Even if it's on the stack, if no subsequent code touches that reference, it's potentially unreachable.

See this other answer for an example of how an object can be GC'ed while a local variable referencing it is still in scope.

Here's an example of how an object can be finalized while an instance method call is active:

class FinalizeThis {
    protected void finalize() {
        System.out.println("finalized!");
    }

    void loop() {
        System.out.println("loop() called");
        for (int i = 0; i < 1_000_000_000; i++) {
            if (i % 1_000_000 == 0)
                System.gc();
        }
        System.out.println("loop() returns");
    }

    public static void main(String[] args) {
        new FinalizeThis().loop();
    }
}

While the loop() method is active, there is no possibility of any code doing anything with the reference to the FinalizeThis object, so it's unreachable. And therefore it can be finalized and GC'ed. On JDK 8 GA, this prints the following:

loop() called
finalized!
loop() returns

every time.

Something similar might be going on with MimeBodyPart. Is it being stored in a local variable? (It seems so, since the code seems to adhere to a convention that fields are named with an m_ prefix.)

UPDATE

In the comments, the OP suggested making the following change:

    public static void main(String[] args) {
        FinalizeThis finalizeThis = new FinalizeThis();
        finalizeThis.loop();
    }

With this change he didn't observe finalization, and neither do I. However, if this further change is made:

    public static void main(String[] args) {
        FinalizeThis finalizeThis = new FinalizeThis();
        for (int i = 0; i < 1_000_000; i++)
            Thread.yield();
        finalizeThis.loop();
    }

finalization once again occurs. I suspect the reason is that without the loop, the main() method is interpreted, not compiled. The interpreter is probably less aggressive about reachability analysis. With the yield loop in place, the main() method gets compiled, and the JIT compiler detects that finalizeThis has become unreachable while the loop() method is executing.

Another way of triggering this behavior is to use the -Xcomp option to the JVM, which forces methods to be JIT-compiled before execution. I wouldn't run an entire application this way -- JIT-compiling everything can be quite slow and take lots of space -- but it's useful for flushing out cases like this in little test programs, instead of tinkering with loops.

answered Mar 5, 2019 by developer_1
• 3,350 points

Related Questions In Java

0 votes
2 answers

One line initialization of an ArrayList object in Java

In Java 8 or earlier: List<String> string = ...READ MORE

answered Jul 26, 2018 in Java by samarth295
• 2,220 points
4,758 views
+17 votes
25 answers

How can I convert String to JSON object in Java?

Hi @Daisy You can use Google gson  for more ...READ MORE

answered Feb 7, 2019 in Java by Suresh
• 720 points
253,619 views
0 votes
1 answer

Perform "Switch-case" on String in Java

Switches based on integers can be optimized ...READ MORE

answered May 8, 2018 in Java by Rishabh
• 3,620 points
952 views
0 votes
1 answer

Need for finalize() in Java

finalize() is a method called by the ...READ MORE

answered May 9, 2018 in Java by code.reaper12
• 3,500 points
687 views
0 votes
1 answer

How to install Java 8 on Mac

Oracle has a poor record for making ...READ MORE

answered Dec 23, 2020 in Java by Gitika
• 65,770 points
2,304 views
+5 votes
4 answers

How to execute a python file with few arguments in java?

You can use Java Runtime.exec() to run python script, ...READ MORE

answered Mar 27, 2018 in Java by DragonLord999
• 8,450 points

edited Nov 7, 2018 by Omkar 81,223 views
+1 vote
1 answer

How to handle drop downs using Selenium WebDriver in Java

First, find an XPath which will return ...READ MORE

answered Mar 27, 2018 in Selenium by nsv999
• 5,500 points
8,359 views
0 votes
1 answer

What are the differences between getText() and getAttribute() functions in Selenium WebDriver?

See, both are used to retrieve something ...READ MORE

answered Apr 5, 2018 in Selenium by nsv999
• 5,500 points
17,433 views
0 votes
3 answers

Check if a String is numeric in Java

Java 8 Lambda Expression is used: String someString ...READ MORE

answered Sep 3, 2018 in Java by Daisy
• 8,140 points
3,699 views
0 votes
3 answers

How can I add new elements to an Array in Java

String[] source = new String[] { "a", ...READ MORE

answered Sep 19, 2018 in Java by Sushmita
• 6,920 points
13,192 views
webinar REGISTER FOR FREE WEBINAR X
REGISTER NOW
webinar_success Thank you for registering Join Edureka Meetup community for 100+ Free Webinars each month JOIN MEETUP GROUP