8036666: VMTI GetObjectMonitorUsage does not return correct recursion count
authorasiebenborn
Tue, 11 Mar 2014 15:22:44 +0100
changeset 23518 00028482ef09
parent 23517 27c4307d6cda
child 23519 9a78876cefeb
8036666: VMTI GetObjectMonitorUsage does not return correct recursion count Summary: fix object lock recursion count and add test Reviewed-by: sspitsyn, dsamersoff
hotspot/src/share/vm/prims/jvmtiEnvBase.cpp
hotspot/test/serviceability/jvmti/8036666/GetObjectLockCount.java
hotspot/test/serviceability/jvmti/8036666/RecursiveObjectLock.java
--- a/hotspot/src/share/vm/prims/jvmtiEnvBase.cpp	Fri Mar 28 21:04:37 2014 -0700
+++ b/hotspot/src/share/vm/prims/jvmtiEnvBase.cpp	Tue Mar 11 15:22:44 2014 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2014, 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
@@ -1020,19 +1020,12 @@
     }
 
     if (owning_thread != NULL) {  // monitor is owned
-      if ((address)owning_thread == owner) {
-        // the owner field is the JavaThread *
-        assert(mon != NULL,
-          "must have heavyweight monitor with JavaThread * owner");
-        ret.entry_count = mon->recursions() + 1;
-      } else {
-        // The owner field is the Lock word on the JavaThread's stack
-        // so the recursions field is not valid. We have to count the
-        // number of recursive monitor entries the hard way. We pass
-        // a handle to survive any GCs along the way.
-        ResourceMark rm;
-        ret.entry_count = count_locked_objects(owning_thread, hobj);
-      }
+      // The recursions field of a monitor does not reflect recursions
+      // as lightweight locks before inflating the monitor are not included.
+      // We have to count the number of recursive monitor entries the hard way.
+      // We pass a handle to survive any GCs along the way.
+      ResourceMark rm;
+      ret.entry_count = count_locked_objects(owning_thread, hobj);
     }
     // implied else: entry_count == 0
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/serviceability/jvmti/8036666/GetObjectLockCount.java	Tue Mar 11 15:22:44 2014 +0100
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2014 SAP AG.  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.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.sun.jdi.AbsentInformationException;
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.LocalVariable;
+import com.sun.jdi.Location;
+import com.sun.jdi.ObjectReference;
+import com.sun.jdi.ReferenceType;
+import com.sun.jdi.StackFrame;
+import com.sun.jdi.ThreadReference;
+import com.sun.jdi.Value;
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.Connector.Argument;
+import com.sun.jdi.connect.IllegalConnectorArgumentsException;
+import com.sun.jdi.connect.LaunchingConnector;
+import com.sun.jdi.connect.VMStartException;
+import com.sun.jdi.event.BreakpointEvent;
+import com.sun.jdi.event.ClassPrepareEvent;
+import com.sun.jdi.event.Event;
+import com.sun.jdi.event.EventQueue;
+import com.sun.jdi.event.EventSet;
+import com.sun.jdi.event.VMDeathEvent;
+import com.sun.jdi.event.VMDisconnectEvent;
+import com.sun.jdi.event.VMStartEvent;
+import com.sun.jdi.request.BreakpointRequest;
+import com.sun.jdi.request.ClassPrepareRequest;
+import com.sun.jdi.request.EventRequestManager;
+
+
+/*
+ * @test GetObjectLockCount.java
+ * @bug 8036666
+ * @key regression
+ * @summary verify jvm returns correct lock recursion count
+ * @run compile -g RecursiveObjectLock.java
+ * @run main/othervm GetObjectLockCount
+ * @author axel.siebenborn@sap.com
+ */
+
+public class GetObjectLockCount {
+
+    public static final String CLASS_NAME  = "RecursiveObjectLock";
+    public static final String METHOD_NAME = "breakpoint1";
+    public static final String ARGUMENTS = "";
+
+
+    /**
+     * Find a com.sun.jdi.CommandLineLaunch connector
+     */
+    static LaunchingConnector findLaunchingConnector() {
+        List <Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
+        Iterator <Connector> iter = connectors.iterator();
+        while (iter.hasNext()) {
+            Connector connector = iter.next();
+            if (connector.name().equals("com.sun.jdi.CommandLineLaunch")) {
+                return (LaunchingConnector)connector;
+            }
+        }
+        throw new Error("No launching connector");
+    }
+
+    static VirtualMachine launchTarget(String mainArgs) {
+        LaunchingConnector connector = findLaunchingConnector();
+        Map<String, Argument>  arguments = connectorArguments(connector, mainArgs);
+        try {
+            return (VirtualMachine) connector.launch(arguments);
+        } catch (IOException exc) {
+            throw new Error("Unable to launch target VM: " + exc);
+        } catch (IllegalConnectorArgumentsException exc) {
+            throw new Error("Internal error: " + exc);
+        } catch (VMStartException exc) {
+            throw new Error("Target VM failed to initialize: " +
+                    exc.getMessage());
+        }
+    }
+    /**
+     * Return the launching connector's arguments.
+     */
+    static Map <String,Connector.Argument> connectorArguments(LaunchingConnector connector, String mainArgs) {
+        Map<String,Connector.Argument> arguments = connector.defaultArguments();
+
+        Connector.Argument mainArg = (Connector.Argument)arguments.get("main");
+        if (mainArg == null) {
+            throw new Error("Bad launching connector");
+        }
+        mainArg.setValue(mainArgs);
+
+        Connector.Argument optionsArg = (Connector.Argument)arguments.get("options");
+        if (optionsArg == null) {
+            throw new Error("Bad launching connector");
+        }
+        optionsArg.setValue(ARGUMENTS);
+        return arguments;
+    }
+
+    private static void addClassWatch(VirtualMachine vm) {
+        EventRequestManager erm = vm.eventRequestManager();
+        ClassPrepareRequest classPrepareRequest = erm
+                .createClassPrepareRequest();
+        classPrepareRequest.addClassFilter(CLASS_NAME);
+        classPrepareRequest.setEnabled(true);
+    }
+
+    private static void addBreakpoint(VirtualMachine vm, ReferenceType refType) {
+        Location breakpointLocation = null;
+        List<Location> locs;
+        try {
+            locs = refType.allLineLocations();
+            for (Location loc: locs) {
+                if (loc.method().name().equals(METHOD_NAME)) {
+                    breakpointLocation = loc;
+                    break;
+                }
+            }
+        } catch (AbsentInformationException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        if (breakpointLocation != null) {
+            EventRequestManager evtReqMgr = vm.eventRequestManager();
+            BreakpointRequest bReq = evtReqMgr.createBreakpointRequest(breakpointLocation);
+            bReq.setSuspendPolicy(BreakpointRequest.SUSPEND_ALL);
+            bReq.enable();
+        }
+    }
+
+    /**
+     * @param args
+     * @throws InterruptedException
+     */
+    public static void main(String[] args) throws InterruptedException  {
+
+        VirtualMachine vm = launchTarget(CLASS_NAME);
+
+        // process events
+        EventQueue eventQueue = vm.eventQueue();
+        // resume the vm
+        boolean launched = false;
+
+        while (!launched) {
+            EventSet eventSet = eventQueue.remove();
+            for (Event event : eventSet) {
+                if (event instanceof VMStartEvent) {
+                    System.out.println("Vm launched");
+                    // set watch field on already loaded classes
+                    List<ReferenceType> referenceTypes = vm.classesByName(CLASS_NAME);
+                    for (ReferenceType refType : referenceTypes) {
+                        System.out.println("Found Class");
+                        addBreakpoint(vm, refType);
+                    }
+
+                    // watch for loaded classes
+                    addClassWatch(vm);
+                    vm.resume();
+                    launched = true;
+                }
+            }
+        }
+
+        Process process = vm.process();
+
+        // Copy target's output and error to our output and error.
+        Thread outThread = new StreamRedirectThread("out reader", process.getInputStream());
+        Thread errThread = new StreamRedirectThread("error reader", process.getErrorStream());
+
+        int recursionCount = -1;
+
+        errThread.start();
+        outThread.start();
+        boolean connected = true;
+        while (connected) {
+            EventSet eventSet = eventQueue.remove();
+            for (Event event : eventSet) {
+                if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {
+                    // exit
+                    connected = false;
+                }
+                else if (event instanceof ClassPrepareEvent) {
+                    // watch field on loaded class
+                    System.out.println("ClassPrepareEvent");
+                    ClassPrepareEvent classPrepEvent = (ClassPrepareEvent) event;
+                    ReferenceType refType = classPrepEvent.referenceType();
+                    addBreakpoint(vm, refType);
+                } else if (event instanceof BreakpointEvent) {
+                    recursionCount = getLockRecursions(vm);
+                    System.out.println("resume...");
+                }
+            }
+            eventSet.resume();
+        }
+        // Shutdown begins when event thread terminates
+        try {
+            errThread.join(); // Make sure output is forwarded
+            outThread.join();
+        } catch (InterruptedException e) {
+            // we don't interrupt
+            e.printStackTrace();
+        }
+        if (recursionCount != 3) {
+            throw new AssertionError("recursions: expected 3, but was " + recursionCount);
+        }
+    }
+
+    public static int getLockRecursions(VirtualMachine vm) {
+        List <ThreadReference> threads = vm.allThreads();
+        for (ThreadReference thread : threads) {
+            if (thread.name().equals("main")) {
+
+                System.out.println("Found main thread.");
+                try{
+                    StackFrame frame = thread.frame(3);
+                    return frame.thisObject().entryCount();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            System.out.println("Main thread not found!");
+        }
+        return -1;
+    }
+}
+
+class StreamRedirectThread extends Thread {
+
+    private final BufferedReader in;
+
+    private static final int BUFFER_SIZE = 2048;
+
+    /**
+     * Set up for copy.
+     * @param name  Name of the thread
+     * @param in    Stream to copy from
+     */
+    StreamRedirectThread(String name, InputStream in) {
+        super(name);
+        this.in = new BufferedReader(new InputStreamReader(in));
+    }
+
+    /**
+     * Copy.
+     */
+    public void run() {
+        try {
+            String line;
+            while ((line = in.readLine ()) != null) {
+                System.out.println("testvm: " + line);
+            }
+            System.out.flush();
+        } catch(IOException exc) {
+            System.err.println("Child I/O Transfer - " + exc);
+            exc.printStackTrace();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/serviceability/jvmti/8036666/RecursiveObjectLock.java	Tue Mar 11 15:22:44 2014 +0100
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014 SAP AG.  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.
+ */
+
+public class RecursiveObjectLock {
+
+    public void testMethod() {
+        synchronized (this) {
+            nestedLock1();
+        }
+    }
+
+    public void nestedLock1() {
+        synchronized (this) {
+            nestedLock2();
+        }
+    }
+
+    public void nestedLock2() {
+        synchronized (this) {
+            callWait();
+        }
+    }
+
+    public void callWait(){
+        try {
+            this.wait(1);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        breakpoint1();
+    }
+
+    public static void breakpoint1() {
+        // purpose: hold a breakpoint
+    }
+
+    public static void main(String[] args) {
+        RecursiveObjectLock ro = new RecursiveObjectLock();
+        ro.testMethod();
+        System.out.println("ready");
+    }
+
+}