提交 e64e8985 编写于 作者: D dcubed

6942989: 2/2 Memory leak of java.lang.ref.WeakReference objects

Summary: Use ReferenceQueues to manage WeakReferences in LogManager and Logger.
Reviewed-by: dholmes, alanb, emcmanus, tonyp
Contributed-by: jeremymanson@google.com
上级 ec8847a5
/*
* 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
......@@ -29,6 +29,7 @@ package java.util.logging;
import java.io.*;
import java.util.*;
import java.security.*;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
......@@ -154,10 +155,10 @@ public class LogManager {
= new PropertyChangeSupport(LogManager.class);
private final static Level defaultLevel = Level.INFO;
// Table of known loggers. Maps names to Loggers.
private Hashtable<String,WeakReference<Logger>> loggers =
new Hashtable<String,WeakReference<Logger>>();
// Tree of known loggers
// Table of named Loggers that maps names to Loggers.
private Hashtable<String,LoggerWeakRef> namedLoggers =
new Hashtable<String,LoggerWeakRef>();
// Tree of named Loggers
private LogNode root = new LogNode(null);
private Logger rootLogger;
......@@ -417,6 +418,121 @@ public class LogManager {
}});
}
// loggerRefQueue holds LoggerWeakRef objects for Logger objects
// that have been GC'ed.
private final ReferenceQueue<Logger> loggerRefQueue
= new ReferenceQueue<Logger>();
// Package-level inner class.
// Helper class for managing WeakReferences to Logger objects.
//
// LogManager.namedLoggers
// - has weak references to all named Loggers
// - namedLoggers keeps the LoggerWeakRef objects for the named
// Loggers around until we can deal with the book keeping for
// the named Logger that is being GC'ed.
// LogManager.LogNode.loggerRef
// - has a weak reference to a named Logger
// - the LogNode will also keep the LoggerWeakRef objects for
// the named Loggers around; currently LogNodes never go away.
// Logger.kids
// - has a weak reference to each direct child Logger; this
// includes anonymous and named Loggers
// - anonymous Loggers are always children of the rootLogger
// which is a strong reference; rootLogger.kids keeps the
// LoggerWeakRef objects for the anonymous Loggers around
// until we can deal with the book keeping.
//
final class LoggerWeakRef extends WeakReference<Logger> {
private String name; // for namedLoggers cleanup
private LogNode node; // for loggerRef cleanup
private WeakReference<Logger> parentRef; // for kids cleanup
LoggerWeakRef(Logger logger) {
super(logger, loggerRefQueue);
name = logger.getName(); // save for namedLoggers cleanup
}
// dispose of this LoggerWeakRef object
void dispose() {
if (node != null) {
// if we have a LogNode, then we were a named Logger
// so clear namedLoggers weak ref to us
manager.namedLoggers.remove(name);
name = null; // clear our ref to the Logger's name
node.loggerRef = null; // clear LogNode's weak ref to us
node = null; // clear our ref to LogNode
}
if (parentRef != null) {
// this LoggerWeakRef has or had a parent Logger
Logger parent = parentRef.get();
if (parent != null) {
// the parent Logger is still there so clear the
// parent Logger's weak ref to us
parent.removeChildLogger(this);
}
parentRef = null; // clear our weak ref to the parent Logger
}
}
// set the node field to the specified value
void setNode(LogNode node) {
this.node = node;
}
// set the parentRef field to the specified value
void setParentRef(WeakReference<Logger> parentRef) {
this.parentRef = parentRef;
}
}
// Package-level method.
// Drain some Logger objects that have been GC'ed.
//
// drainLoggerRefQueueBounded() is called by addLogger() below
// and by Logger.getAnonymousLogger(String) so we'll drain up to
// MAX_ITERATIONS GC'ed Loggers for every Logger we add.
//
// On a WinXP VMware client, a MAX_ITERATIONS value of 400 gives
// us about a 50/50 mix in increased weak ref counts versus
// decreased weak ref counts in the AnonLoggerWeakRefLeak test.
// Here are stats for cleaning up sets of 400 anonymous Loggers:
// - test duration 1 minute
// - sample size of 125 sets of 400
// - average: 1.99 ms
// - minimum: 0.57 ms
// - maximum: 25.3 ms
//
// The same config gives us a better decreased weak ref count
// than increased weak ref count in the LoggerWeakRefLeak test.
// Here are stats for cleaning up sets of 400 named Loggers:
// - test duration 2 minutes
// - sample size of 506 sets of 400
// - average: 0.57 ms
// - minimum: 0.02 ms
// - maximum: 10.9 ms
//
private final static int MAX_ITERATIONS = 400;
final synchronized void drainLoggerRefQueueBounded() {
for (int i = 0; i < MAX_ITERATIONS; i++) {
if (loggerRefQueue == null) {
// haven't finished loading LogManager yet
break;
}
LoggerWeakRef ref = (LoggerWeakRef) loggerRefQueue.poll();
if (ref == null) {
break;
}
// a Logger object has been GC'ed so clean it up
ref.dispose();
}
}
/**
* Add a named logger. This does nothing and returns false if a logger
* with the same name is already registered.
......@@ -439,13 +555,16 @@ public class LogManager {
throw new NullPointerException();
}
WeakReference<Logger> ref = loggers.get(name);
// cleanup some Loggers that have been GC'ed
drainLoggerRefQueueBounded();
LoggerWeakRef ref = namedLoggers.get(name);
if (ref != null) {
if (ref.get() == null) {
// Hashtable holds stale weak reference
// to a logger which has been GC-ed.
// Allow to register new one.
loggers.remove(name);
// It's possible that the Logger was GC'ed after the
// drainLoggerRefQueueBounded() call above so allow
// a new one to be registered.
namedLoggers.remove(name);
} else {
// We already have a registered logger with the given name.
return false;
......@@ -454,7 +573,8 @@ public class LogManager {
// We're adding a new logger.
// Note that we are creating a weak reference here.
loggers.put(name, new WeakReference<Logger>(logger));
ref = new LoggerWeakRef(logger);
namedLoggers.put(name, ref);
// Apply any initial level defined for the new logger.
Level level = getLevelProperty(name+".level", null);
......@@ -469,11 +589,11 @@ public class LogManager {
// Find the new node and its parent.
LogNode node = findNode(name);
node.loggerRef = new WeakReference<Logger>(logger);
node.loggerRef = ref;
Logger parent = null;
LogNode nodep = node.parent;
while (nodep != null) {
WeakReference<Logger> nodeRef = nodep.loggerRef;
LoggerWeakRef nodeRef = nodep.loggerRef;
if (nodeRef != null) {
parent = nodeRef.get();
if (parent != null) {
......@@ -489,6 +609,9 @@ public class LogManager {
// Walk over the children and tell them we are their new parent.
node.walkAndSetParent(logger);
// new LogNode is ready so tell the LoggerWeakRef about it
ref.setNode(node);
return true;
}
......@@ -572,7 +695,7 @@ public class LogManager {
* @return matching logger or null if none is found
*/
public synchronized Logger getLogger(String name) {
WeakReference<Logger> ref = loggers.get(name);
LoggerWeakRef ref = namedLoggers.get(name);
if (ref == null) {
return null;
}
......@@ -580,7 +703,7 @@ public class LogManager {
if (logger == null) {
// Hashtable holds stale weak reference
// to a logger which has been GC-ed.
loggers.remove(name);
namedLoggers.remove(name);
}
return logger;
}
......@@ -594,7 +717,7 @@ public class LogManager {
* @return enumeration of logger name strings
*/
public synchronized Enumeration<String> getLoggerNames() {
return loggers.keys();
return namedLoggers.keys();
}
/**
......@@ -942,7 +1065,7 @@ public class LogManager {
// Nested class to represent a node in our tree of named loggers.
private static class LogNode {
HashMap<String,LogNode> children;
WeakReference<Logger> loggerRef;
LoggerWeakRef loggerRef;
LogNode parent;
LogNode(LogNode parent) {
......@@ -958,7 +1081,7 @@ public class LogManager {
Iterator<LogNode> values = children.values().iterator();
while (values.hasNext()) {
LogNode node = values.next();
WeakReference<Logger> ref = node.loggerRef;
LoggerWeakRef ref = node.loggerRef;
Logger logger = (ref == null) ? null : ref.get();
if (logger == null) {
node.walkAndSetParent(parent);
......
/*
* 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
......@@ -183,7 +183,7 @@ public class Logger {
// We keep weak references from parents to children, but strong
// references from children to parents.
private volatile Logger parent; // our nearest parent.
private ArrayList<WeakReference<Logger>> kids; // WeakReferences to loggers that have us as parent
private ArrayList<LogManager.LoggerWeakRef> kids; // WeakReferences to loggers that have us as parent
private volatile Level levelObject;
private volatile int levelValue; // current effective level value
......@@ -366,13 +366,8 @@ public class Logger {
*
* @return a newly created private Logger
*/
public static synchronized Logger getAnonymousLogger() {
LogManager manager = LogManager.getLogManager();
Logger result = new Logger(null, null);
result.anonymous = true;
Logger root = manager.getLogger("");
result.doSetParent(root);
return result;
public static Logger getAnonymousLogger() {
return getAnonymousLogger(null);
}
/**
......@@ -401,6 +396,8 @@ public class Logger {
*/
public static synchronized Logger getAnonymousLogger(String resourceBundleName) {
LogManager manager = LogManager.getLogManager();
// cleanup some Loggers that have been GC'ed
manager.drainLoggerRefQueueBounded();
Logger result = new Logger(null, resourceBundleName);
result.anonymous = true;
Logger root = manager.getLogger("");
......@@ -1380,14 +1377,18 @@ public class Logger {
synchronized (treeLock) {
// Remove ourself from any previous parent.
LogManager.LoggerWeakRef ref = null;
if (parent != null) {
// assert parent.kids != null;
for (Iterator<WeakReference<Logger>> iter = parent.kids.iterator(); iter.hasNext(); ) {
WeakReference<Logger> ref = iter.next();
for (Iterator<LogManager.LoggerWeakRef> iter = parent.kids.iterator(); iter.hasNext(); ) {
ref = iter.next();
Logger kid = ref.get();
if (kid == this) {
// ref is used down below to complete the reparenting
iter.remove();
break;
} else {
ref = null;
}
}
// We have now removed ourself from our parents' kids.
......@@ -1396,9 +1397,14 @@ public class Logger {
// Set our new parent.
parent = newParent;
if (parent.kids == null) {
parent.kids = new ArrayList<WeakReference<Logger>>(2);
parent.kids = new ArrayList<LogManager.LoggerWeakRef>(2);
}
parent.kids.add(new WeakReference<Logger>(this));
if (ref == null) {
// we didn't have a previous parent
ref = manager.new LoggerWeakRef(this);
}
ref.setParentRef(new WeakReference<Logger>(parent));
parent.kids.add(ref);
// As a result of the reparenting, the effective level
// may have changed for us and our children.
......@@ -1407,6 +1413,21 @@ public class Logger {
}
}
// Package-level method.
// Remove the weak reference for the specified child Logger from the
// kid list. We should only be called from LoggerWeakRef.dispose().
final void removeChildLogger(LogManager.LoggerWeakRef child) {
synchronized (treeLock) {
for (Iterator<LogManager.LoggerWeakRef> iter = kids.iterator(); iter.hasNext(); ) {
LogManager.LoggerWeakRef ref = iter.next();
if (ref == child) {
iter.remove();
return;
}
}
}
}
// Recalculate the effective level for this node and
// recursively for our children.
......@@ -1438,7 +1459,7 @@ public class Logger {
// Recursively update the level on each of our kids.
if (kids != null) {
for (int i = 0; i < kids.size(); i++) {
WeakReference<Logger> ref = kids.get(i);
LogManager.LoggerWeakRef ref = kids.get(i);
Logger kid = ref.get();
if (kid != null) {
kid.updateEffectiveLevel();
......
/*
* 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);
}
}
#
# 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> <class_name>
#
# Output format for 'jmap -histo:live':
#
# <num>: <#instances> <#bytes> <class_name>
#
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 <num>: field; does nothing in JDK1.5.0
s/^[1-9][0-9]*:[ ][ ]*//
# strip <class_name> 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
/*
* 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);
}
}
#
# 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> <class_name>
#
# Output format for 'jmap -histo:live':
#
# <num>: <#instances> <#bytes> <class_name>
#
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 <num>: field; does nothing in JDK1.5.0
s/^[1-9][0-9]*:[ ][ ]*//
# strip <class_name> 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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册