--- old/src/share/classes/java/util/logging/LogManager.java Thu Jun 10 16:27:56 2010 +++ new/src/share/classes/java/util/logging/LogManager.java Thu Jun 10 16:27:52 2010 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -478,6 +478,12 @@ parent = nodeRef.get(); if (parent != null) { break; + } else { + // nodep holds a stale weak reference to a Logger + // which has been GC-ed. Note this will only cleanup + // stale weak refs that we encounter before we find + // our parent LogNode. + nodep.loggerRef = null; } } nodep = nodep.parent; @@ -489,6 +495,16 @@ // Walk over the children and tell them we are their new parent. node.walkAndSetParent(logger); + if (node.parent != null) { + // Look for possible weak reference cleanup from the new + // parent LogNode down. The walkAndSetParent() call above + // might have already done some or none of this work so + // this call is the only way to be absolutely sure we have + // checked for stale weak refs in every LogNode in the + // parent LogNode's hierarchy. + node.parent.deleteStaleWeakRefs(); + } + return true; } @@ -961,6 +977,11 @@ WeakReference ref = node.loggerRef; Logger logger = (ref == null) ? null : ref.get(); if (logger == null) { + // node holds a stale weak reference to a Logger + // which has been GC-ed. Note this will only cleanup + // stale weak refs that we encounter during our walk + // from the original node. + node.loggerRef = null; node.walkAndSetParent(parent); } else { doSetParent(logger, parent); @@ -967,6 +988,27 @@ } } } + + // Recursively delete stale WeakReferences on each of our children. + void deleteStaleWeakRefs() { + if (children == null) { + return; + } + Iterator values = children.values().iterator(); + while (values.hasNext()) { + LogNode node = values.next(); + WeakReference ref = node.loggerRef; + if (ref != null) { + Logger logger = ref.get(); + if (logger == null) { + // node holds a stale weak reference to a Logger + // which has been GC-ed. + node.loggerRef = null; + } + } + node.deleteStaleWeakRefs(); + } + } } // We use a subclass of Logger for the root logger, so --- old/src/share/classes/java/util/logging/Logger.java Thu Jun 10 16:28:03 2010 +++ new/src/share/classes/java/util/logging/Logger.java Thu Jun 10 16:28:00 2010 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -184,6 +184,9 @@ // references from children to parents. private volatile Logger parent; // our nearest parent. private ArrayList> kids; // WeakReferences to loggers that have us as parent + // marker to know if this Logger has already been visited during the + // current WeakReference cleanup pass + private int kidsCleanupMarker; private volatile Level levelObject; private volatile int levelValue; // current effective level value @@ -1388,6 +1391,14 @@ if (kid == this) { iter.remove(); break; + } else if (kid == null) { + // Since we're already iterating the previous + // parent's kid list, remove any stale weak + // references to Logger objects that have been + // GC'ed. Note this will only cleanup stale weak + // refs that we encounter before we find ourself + // on the kids list. + iter.remove(); } } // We have now removed ourself from our parents' kids. @@ -1404,6 +1415,15 @@ // may have changed for us and our children. updateEffectiveLevel(); + // Look for possible weak reference cleanup from the new + // parent Logger down. The updateEffectiveLevel() call above + // might have already done some or none of this work so + // this call is the only way to be absolutely sure we have + // have checked for stale weak refs in every Logger in the + // parent Logger's hierarchy. See deleteStaleWeakRefs() + // below for why this marker is needed. + parent.kidsCleanupMarker = (int) System.currentTimeMillis(); + parent.deleteStaleWeakRefs(parent.kidsCleanupMarker); } } @@ -1437,13 +1457,44 @@ // Recursively update the level on each of our kids. if (kids != null) { - for (int i = 0; i < kids.size(); i++) { - WeakReference ref = kids.get(i); + for (Iterator> iter = kids.iterator(); + iter.hasNext(); ) { + WeakReference ref = iter.next(); Logger kid = ref.get(); if (kid != null) { kid.updateEffectiveLevel(); + } else { + // Since we're already iterating this kid list, + // remove any stale weak references to Logger + // objects that have been GC'ed. Note this will + // only cleanup stale weak refs that we encounter + // when we have to update the effective Level value. + iter.remove(); } } + } + } + + + // Recursively delete stale WeakReferences on each of our kids. + // The marker parameter is used to know if a kid has been visited + // before. This marker logic is needed because the Logging API + // currently permits loops to be created in the Logger hierarchy. + private void deleteStaleWeakRefs(int marker) { + if (kids != null) { + for (Iterator> iter = kids.iterator(); + iter.hasNext(); ) { + WeakReference ref = iter.next(); + Logger kid = ref.get(); + if (kid == null) { + // Logger has been GC'ed so delete the stale weak ref + iter.remove(); + } else if (kid.kidsCleanupMarker != marker) { + // visit the non-GC'ed Logger (and its kids, if any) + kid.kidsCleanupMarker = marker; + kid.deleteStaleWeakRefs(marker); + } + } } } --- /dev/null Thu Jun 10 16:28:11 2010 +++ new/test/java/util/logging/AnonLoggerWeakRefLeak.java Thu Jun 10 16:28:08 2010 @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.logging.*; + +public class AnonLoggerWeakRefLeak { + public static int DEFAULT_LOOP_TIME = 60; // time is in seconds + + public static void main(String[] args) { + int loop_time = 0; + int max_loop_time = DEFAULT_LOOP_TIME; + + if (args.length == 0) { + System.out.println("INFO: using default time of " + + max_loop_time + " seconds."); + } else { + try { + max_loop_time = Integer.parseInt(args[0]); + } catch (NumberFormatException nfe) { + System.err.println("Error: '" + args[0] + + "': is not a valid seconds value."); + System.err.println("Usage: AnonLoggerWeakRefLeak [seconds]"); + System.exit(1); + } + } + + long count = 0; + long now = 0; + long startTime = System.currentTimeMillis(); + + while (now < (startTime + (max_loop_time * 1000))) { + if ((count % 1000) == 0) { + // Print initial call count to let caller know that + // we're up and running and then periodically + System.out.println("INFO: call count = " + count); + } + + for (int i = 0; i < 100; i++) { + // this Logger call is leaking a WeakReference in Logger.kids + java.util.logging.Logger.getAnonymousLogger(); + count++; + } + + try { + // delay for 1/10 of a second to avoid CPU saturation + Thread.sleep(100); + } catch (InterruptedException ie) { + // ignore any exceptions + } + + now = System.currentTimeMillis(); + } + + System.out.println("INFO: final loop count = " + count); + } +} --- /dev/null Thu Jun 10 16:28:18 2010 +++ new/test/java/util/logging/AnonLoggerWeakRefLeak.sh Thu Jun 10 16:28:14 2010 @@ -0,0 +1,246 @@ +# +# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# @test +# @bug 6942989 +# @summary Check for WeakReference leak in anonymous Logger objects +# @author Daniel D. Daugherty +# +# @run build AnonLoggerWeakRefLeak +# @run shell/timeout=180 AnonLoggerWeakRefLeak.sh + +# The timeout is: 2 minutes for infrastructure and 1 minute for the test +# + +if [ "${TESTJAVA}" = "" ] +then + echo "TESTJAVA not set. Test cannot execute. Failed." + exit 1 +fi + +if [ "${TESTSRC}" = "" ] +then + echo "TESTSRC not set. Test cannot execute. Failed." + exit 1 +fi + +if [ "${TESTCLASSES}" = "" ] +then + echo "TESTCLASSES not set. Test cannot execute. Failed." + exit 1 +fi + +JAVA="${TESTJAVA}"/bin/java +JMAP="${TESTJAVA}"/bin/jmap +JPS="${TESTJAVA}"/bin/jps + +set -eu + +TEST_NAME="AnonLoggerWeakRefLeak" +TARGET_CLASS="java\.lang\.ref\.WeakReference" + +is_cygwin=false +is_mks=false +is_windows=false + +case `uname -s` in +CYGWIN*) + is_cygwin=true + is_windows=true + ;; +Windows_*) + is_mks=true + is_windows=true + ;; +*) + ;; +esac + + +# wrapper for grep +# +grep_cmd() { + set +e + if $is_windows; then + # need dos2unix to get rid of CTRL-M chars from java output + dos2unix | grep "$@" + status="$?" + else + grep "$@" + status="$?" + fi + set -e +} + + +# MAIN begins here +# + +seconds= +if [ "$#" -gt 0 ]; then + seconds="$1" +fi + +# see if this version of jmap supports the '-histo:live' option +jmap_option="-histo:live" +set +e +"${JMAP}" "$jmap_option" 0 > "$TEST_NAME.jmap" 2>&1 +grep '^Usage: ' "$TEST_NAME.jmap" > /dev/null 2>&1 +status="$?" +set -e +if [ "$status" = 0 ]; then + echo "INFO: switching jmap option from '$jmap_option'\c" + jmap_option="-histo" + echo " to '$jmap_option'." +fi + +"${JAVA}" ${TESTVMOPTS} -classpath "${TESTCLASSES}" \ + "$TEST_NAME" $seconds > "$TEST_NAME.log" 2>&1 & +test_pid="$!" +echo "INFO: starting $TEST_NAME as pid = $test_pid" + +# wait for test program to get going +count=0 +while [ "$count" -lt 30 ]; do + sleep 2 + grep_cmd '^INFO: call count = 0$' < "$TEST_NAME.log" > /dev/null 2>&1 + if [ "$status" = 0 ]; then + break + fi + count=`expr $count + 1` +done + +if [ "$count" -ge 30 ]; then + echo "ERROR: $TEST_NAME failed to get going." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 1 +elif [ "$count" -gt 1 ]; then + echo "INFO: $TEST_NAME took $count loops to start." +fi + +if $is_cygwin; then + # We need the Windows pid for jmap and not the Cygwin pid. + # Note: '\t' works on Cygwin, but doesn't seem to work on Solaris. + jmap_pid=`"${JPS}"| grep_cmd "[ \t]$TEST_NAME$" | sed 's/[ \t].*//'` + if [ -z "$jmap_pid" ]; then + echo "FAIL: jps could not map Cygwin pid to Windows pid." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 2 + fi + echo "INFO: pid = $test_pid maps to Windows pid = $jmap_pid" +else + jmap_pid="$test_pid" +fi + +decreasing_cnt=0 +increasing_cnt=0 +loop_cnt=0 +prev_instance_cnt=0 + +while true; do + # Output format for 'jmap -histo' in JDK1.5.0: + # + # <#bytes> <#instances> + # + # Output format for 'jmap -histo:live': + # + # : <#instances> <#bytes> + # + set +e + "${JMAP}" "$jmap_option" "$jmap_pid" > "$TEST_NAME.jmap" 2>&1 + status="$?" + set -e + + if [ "$status" != 0 ]; then + echo "INFO: jmap exited with exit code = $status" + if [ "$loop_cnt" = 0 ]; then + echo "INFO: on the first iteration so no samples were taken." + echo "INFO: start of jmap output:" + cat "$TEST_NAME.jmap" + echo "INFO: end of jmap output." + echo "FAIL: jmap is unable to take any samples." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 2 + fi + echo "INFO: The likely reason is that $TEST_NAME has finished running." + break + fi + + instance_cnt=`grep_cmd "[ ]$TARGET_CLASS$" \ + < "$TEST_NAME.jmap" \ + | sed ' + # strip leading whitespace; does nothing in JDK1.5.0 + s/^[ ][ ]*// + # strip <#bytes> in JDK1.5.0; does nothing otherwise + s/^[1-9][0-9]*[ ][ ]*// + # strip : field; does nothing in JDK1.5.0 + s/^[1-9][0-9]*:[ ][ ]*// + # strip field + s/[ ].*// + '` + if [ -z "$instance_cnt" ]; then + echo "INFO: instance count is unexpectedly empty" + if [ "$loop_cnt" = 0 ]; then + echo "INFO: on the first iteration so no sample was found." + echo "INFO: There is likely a problem with the sed filter." + echo "INFO: start of jmap output:" + cat "$TEST_NAME.jmap" + echo "INFO: end of jmap output." + echo "FAIL: cannot find the instance count value." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 2 + fi + else + echo "INFO: instance_cnt = $instance_cnt" + + if [ "$instance_cnt" -gt "$prev_instance_cnt" ]; then + increasing_cnt=`expr $increasing_cnt + 1` + else + decreasing_cnt=`expr $decreasing_cnt + 1` + fi + prev_instance_cnt="$instance_cnt" + fi + + # delay between samples + sleep 5 + + loop_cnt=`expr $loop_cnt + 1` +done + +echo "INFO: increasing_cnt = $increasing_cnt" +echo "INFO: decreasing_cnt = $decreasing_cnt" + +echo "INFO: The instance count of" `eval echo $TARGET_CLASS` "objects" +if [ "$decreasing_cnt" = 0 ]; then + echo "INFO: is always increasing." + echo "FAIL: This indicates that there is a memory leak." >&2 + exit 2 +fi + +echo "INFO: is both increasing and decreasing." +echo "PASS: This indicates that there is not a memory leak." +exit 0 --- /dev/null Thu Jun 10 16:28:25 2010 +++ new/test/java/util/logging/LoggerWeakRefLeak.java Thu Jun 10 16:28:21 2010 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.logging.*; + +public class LoggerWeakRefLeak { + // AnonLoggerWeakRefLeak checks for one weak reference leak. + // LoggerWeakRefLeak checks for two weak reference leaks so + // this test runs twice as long, by default. + public static int DEFAULT_LOOP_TIME = 120; // time is in seconds + + public static void main(String[] args) { + int loop_time = 0; + int max_loop_time = DEFAULT_LOOP_TIME; + + if (args.length == 0) { + System.out.println("INFO: using default time of " + + max_loop_time + " seconds."); + } else { + try { + max_loop_time = Integer.parseInt(args[0]); + } catch (NumberFormatException nfe) { + System.err.println("Error: '" + args[0] + + "': is not a valid seconds value."); + System.err.println("Usage: LoggerWeakRefLeak [seconds]"); + System.exit(1); + } + } + + long count = 0; + int loggerCount = 0; + long now = 0; + long startTime = System.currentTimeMillis(); + + while (now < (startTime + (max_loop_time * 1000))) { + if ((count % 1000) == 0) { + // Print initial call count to let caller know that + // we're up and running and then periodically + System.out.println("INFO: call count = " + count); + } + + for (int i = 0; i < 100; i++) { + // This Logger call is leaking two different WeakReferences: + // - one in LogManager.LogNode + // - one in Logger.kids + java.util.logging.Logger.getLogger("logger-" + loggerCount); + count++; + if (++loggerCount >= 25000) { + // Limit the Logger namespace used by the test so + // the weak refs in LogManager.loggers that are + // being properly managed don't skew the counts + // by too much. + loggerCount = 0; + } + } + + try { + // delay for 1/10 of a second to avoid CPU saturation + Thread.sleep(100); + } catch (InterruptedException ie) { + // ignore any exceptions + } + + now = System.currentTimeMillis(); + } + + System.out.println("INFO: final loop count = " + count); + } +} --- /dev/null Thu Jun 10 16:28:31 2010 +++ new/test/java/util/logging/LoggerWeakRefLeak.sh Thu Jun 10 16:28:28 2010 @@ -0,0 +1,246 @@ +# +# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# @test +# @bug 6942989 +# @summary Check for WeakReference leak in Logger objects +# @author Daniel D. Daugherty +# +# @run build LoggerWeakRefLeak +# @run shell/timeout=240 LoggerWeakRefLeak.sh + +# The timeout is: 2 minutes for infrastructure and 1 minute for the test +# + +if [ "${TESTJAVA}" = "" ] +then + echo "TESTJAVA not set. Test cannot execute. Failed." + exit 1 +fi + +if [ "${TESTSRC}" = "" ] +then + echo "TESTSRC not set. Test cannot execute. Failed." + exit 1 +fi + +if [ "${TESTCLASSES}" = "" ] +then + echo "TESTCLASSES not set. Test cannot execute. Failed." + exit 1 +fi + +JAVA="${TESTJAVA}"/bin/java +JMAP="${TESTJAVA}"/bin/jmap +JPS="${TESTJAVA}"/bin/jps + +set -eu + +TEST_NAME="LoggerWeakRefLeak" +TARGET_CLASS="java\.lang\.ref\.WeakReference" + +is_cygwin=false +is_mks=false +is_windows=false + +case `uname -s` in +CYGWIN*) + is_cygwin=true + is_windows=true + ;; +Windows_*) + is_mks=true + is_windows=true + ;; +*) + ;; +esac + + +# wrapper for grep +# +grep_cmd() { + set +e + if $is_windows; then + # need dos2unix to get rid of CTRL-M chars from java output + dos2unix | grep "$@" + status="$?" + else + grep "$@" + status="$?" + fi + set -e +} + + +# MAIN begins here +# + +seconds= +if [ "$#" -gt 0 ]; then + seconds="$1" +fi + +# see if this version of jmap supports the '-histo:live' option +jmap_option="-histo:live" +set +e +"${JMAP}" "$jmap_option" 0 > "$TEST_NAME.jmap" 2>&1 +grep '^Usage: ' "$TEST_NAME.jmap" > /dev/null 2>&1 +status="$?" +set -e +if [ "$status" = 0 ]; then + echo "INFO: switching jmap option from '$jmap_option'\c" + jmap_option="-histo" + echo " to '$jmap_option'." +fi + +"${JAVA}" ${TESTVMOPTS} -classpath "${TESTCLASSES}" \ + "$TEST_NAME" $seconds > "$TEST_NAME.log" 2>&1 & +test_pid="$!" +echo "INFO: starting $TEST_NAME as pid = $test_pid" + +# wait for test program to get going +count=0 +while [ "$count" -lt 30 ]; do + sleep 2 + grep_cmd '^INFO: call count = 0$' < "$TEST_NAME.log" > /dev/null 2>&1 + if [ "$status" = 0 ]; then + break + fi + count=`expr $count + 1` +done + +if [ "$count" -ge 30 ]; then + echo "ERROR: $TEST_NAME failed to get going." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 1 +elif [ "$count" -gt 1 ]; then + echo "INFO: $TEST_NAME took $count loops to start." +fi + +if $is_cygwin; then + # We need the Windows pid for jmap and not the Cygwin pid. + # Note: '\t' works on Cygwin, but doesn't seem to work on Solaris. + jmap_pid=`"${JPS}"| grep_cmd "[ \t]$TEST_NAME$" | sed 's/[ \t].*//'` + if [ -z "$jmap_pid" ]; then + echo "FAIL: jps could not map Cygwin pid to Windows pid." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 2 + fi + echo "INFO: pid = $test_pid maps to Windows pid = $jmap_pid" +else + jmap_pid="$test_pid" +fi + +decreasing_cnt=0 +increasing_cnt=0 +loop_cnt=0 +prev_instance_cnt=0 + +while true; do + # Output format for 'jmap -histo' in JDK1.5.0: + # + # <#bytes> <#instances> + # + # Output format for 'jmap -histo:live': + # + # : <#instances> <#bytes> + # + set +e + "${JMAP}" "$jmap_option" "$jmap_pid" > "$TEST_NAME.jmap" 2>&1 + status="$?" + set -e + + if [ "$status" != 0 ]; then + echo "INFO: jmap exited with exit code = $status" + if [ "$loop_cnt" = 0 ]; then + echo "INFO: on the first iteration so no samples were taken." + echo "INFO: start of jmap output:" + cat "$TEST_NAME.jmap" + echo "INFO: end of jmap output." + echo "FAIL: jmap is unable to take any samples." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 2 + fi + echo "INFO: The likely reason is that $TEST_NAME has finished running." + break + fi + + instance_cnt=`grep_cmd "[ ]$TARGET_CLASS$" \ + < "$TEST_NAME.jmap" \ + | sed ' + # strip leading whitespace; does nothing in JDK1.5.0 + s/^[ ][ ]*// + # strip <#bytes> in JDK1.5.0; does nothing otherwise + s/^[1-9][0-9]*[ ][ ]*// + # strip : field; does nothing in JDK1.5.0 + s/^[1-9][0-9]*:[ ][ ]*// + # strip field + s/[ ].*// + '` + if [ -z "$instance_cnt" ]; then + echo "INFO: instance count is unexpectedly empty" + if [ "$loop_cnt" = 0 ]; then + echo "INFO: on the first iteration so no sample was found." + echo "INFO: There is likely a problem with the sed filter." + echo "INFO: start of jmap output:" + cat "$TEST_NAME.jmap" + echo "INFO: end of jmap output." + echo "FAIL: cannot find the instance count value." >&2 + echo "INFO: killing $test_pid" + kill "$test_pid" + exit 2 + fi + else + echo "INFO: instance_cnt = $instance_cnt" + + if [ "$instance_cnt" -gt "$prev_instance_cnt" ]; then + increasing_cnt=`expr $increasing_cnt + 1` + else + decreasing_cnt=`expr $decreasing_cnt + 1` + fi + prev_instance_cnt="$instance_cnt" + fi + + # delay between samples + sleep 5 + + loop_cnt=`expr $loop_cnt + 1` +done + +echo "INFO: increasing_cnt = $increasing_cnt" +echo "INFO: decreasing_cnt = $decreasing_cnt" + +echo "INFO: The instance count of" `eval echo $TARGET_CLASS` "objects" +if [ "$decreasing_cnt" = 0 ]; then + echo "INFO: is always increasing." + echo "FAIL: This indicates that there is a memory leak." >&2 + exit 2 +fi + +echo "INFO: is both increasing and decreasing." +echo "PASS: This indicates that there is not a memory leak." +exit 0