8185796: jstack and clhsdb jstack should show lock objects
authorysuenaga
Mon, 27 Nov 2017 11:20:38 +0530
changeset 48113 af9e4669ca18
parent 48112 a3d565e72f51
child 48114 e6b643827037
8185796: jstack and clhsdb jstack should show lock objects Summary: Modifications to display monitor details with SA jstack Reviewed-by: sspitsyn, jgeorge
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/java_lang_Class.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/CompiledVFrame.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/InterpretedVFrame.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/StackTrace.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/classbrowser/HTMLGenerator.java
test/hotspot/jtreg/serviceability/sa/LingeredAppWithLock.java
test/hotspot/jtreg/serviceability/sa/TestClhsdbJstackLock.java
test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackLock.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Oop.java	Mon Nov 27 11:20:38 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2017, 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
@@ -202,8 +202,7 @@
 
   public boolean verify() { return true;}
 
-  // Package-private routine to speed up ObjectHeap.newOop
-  static Klass getKlassForOopHandle(OopHandle handle) {
+  public static Klass getKlassForOopHandle(OopHandle handle) {
     if (handle == null) {
       return null;
     }
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/java_lang_Class.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/java_lang_Class.java	Mon Nov 27 11:20:38 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2017, 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
@@ -41,6 +41,7 @@
 
   // java.lang.Class fields
   static int klassOffset;
+  static int arrayKlassOffset;
   static IntField oopSizeField;
 
   static {
@@ -56,6 +57,7 @@
     // find them from InstanceKlass for java.lang.Class.
     Type jlc = db.lookupType("java_lang_Class");
     klassOffset = (int) jlc.getCIntegerField("_klass_offset").getValue();
+    arrayKlassOffset = (int) jlc.getCIntegerField("_array_klass_offset").getValue();
     int oopSizeOffset = (int) jlc.getCIntegerField("_oop_size_offset").getValue();
     oopSizeField = new IntField(new NamedFieldIdentifier("oop_size"), oopSizeOffset, true);
   }
@@ -69,4 +71,23 @@
   public static long getOopSize(Oop aClass) {
     return java_lang_Class.oopSizeField.getValue(aClass);
   }
+
+  /**
+   * Returns the Java name for this Java mirror
+   */
+  public static String asExternalName(Oop aClass) {
+    Klass k = java_lang_Class.asKlass(aClass);
+    if (k == null) { // primitive array
+      BasicType type = BasicType.T_VOID;
+      ArrayKlass ak = (ArrayKlass)Metadata.instantiateWrapperFor(
+                             aClass.getHandle().getAddressAt(arrayKlassOffset));
+      if (ak != null) {
+        type = BasicType.intToBasicType(ak.getElementType());
+      }
+      return type.getName();
+    } else {
+      return k.getName().asString();
+    }
+  }
+
 }
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java	Mon Nov 27 11:20:38 2017 +0530
@@ -133,6 +133,27 @@
     return tIllegal;
   }
 
+  public static BasicType intToBasicType(int i) {
+    switch(i) {
+      case tBoolean:     return T_BOOLEAN;
+      case tChar:        return T_CHAR;
+      case tFloat:       return T_FLOAT;
+      case tDouble:      return T_DOUBLE;
+      case tByte:        return T_BYTE;
+      case tShort:       return T_SHORT;
+      case tInt:         return T_INT;
+      case tLong:        return T_LONG;
+      case tObject:      return T_OBJECT;
+      case tArray:       return T_ARRAY;
+      case tVoid:        return T_VOID;
+      case tAddress:     return T_ADDRESS;
+      case tNarrowOop:   return T_NARROWOOP;
+      case tMetadata:    return T_METADATA;
+      case tNarrowKlass: return T_NARROWKLASS;
+      default:           return T_ILLEGAL;
+    }
+  }
+
   public static BasicType charToBasicType(char c) {
     switch( c ) {
     case 'B': return T_BYTE;
@@ -158,6 +179,28 @@
     return type;
   }
 
+  public String getName() {
+    switch (type) {
+      case tBoolean:     return "boolean";
+      case tChar:        return "char";
+      case tFloat:       return "float";
+      case tDouble:      return "double";
+      case tByte:        return "byte";
+      case tShort:       return "short";
+      case tInt:         return "int";
+      case tLong:        return "long";
+      case tObject:      return "object";
+      case tArray:       return "array";
+      case tVoid:        return "void";
+      case tAddress:     return "address";
+      case tNarrowOop:   return "narrow oop";
+      case tMetadata:    return "metadata";
+      case tNarrowKlass: return "narrow klass";
+      case tConflict:    return "conflict";
+      default:           return "ILLEGAL TYPE";
+    }
+  }
+
   //-- Internals only below this point
   private BasicType(int type) {
     this.type = type;
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/CompiledVFrame.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/CompiledVFrame.java	Mon Nov 27 11:20:38 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2017, 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
@@ -127,12 +127,12 @@
   }
 
   /** Returns List<MonitorInfo> */
-  public List   getMonitors() {
+  public List<MonitorInfo> getMonitors() {
     List monitors = getScope().getMonitors();
     if (monitors == null) {
-      return new ArrayList();
+      return new ArrayList<>();
     }
-    List result = new ArrayList(monitors.size());
+    List<MonitorInfo> result = new ArrayList<>(monitors.size());
     for (int i = 0; i < monitors.size(); i++) {
       MonitorValue mv = (MonitorValue) monitors.get(i);
       ScopeValue ov = mv.owner();
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/InterpretedVFrame.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/InterpretedVFrame.java	Mon Nov 27 11:20:38 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2017, 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
@@ -108,8 +108,8 @@
   }
 
   /** Returns List<MonitorInfo> */
-  public List   getMonitors() {
-    List result = new ArrayList(5);
+  public List<MonitorInfo> getMonitors() {
+    List<MonitorInfo> result = new ArrayList<>(5);
     for (BasicObjectLock current = getFrame().interpreterFrameMonitorEnd();
          current.address().lessThan(getFrame().interpreterFrameMonitorBegin().address());
          current = getFrame().nextMonitorInInterpreterFrame(current)) {
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaVFrame.java	Mon Nov 27 11:20:38 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2017, 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
@@ -28,14 +28,19 @@
 import java.util.*;
 import sun.jvm.hotspot.oops.*;
 import sun.jvm.hotspot.utilities.*;
+import sun.jvm.hotspot.debugger.*;
 
 public abstract class JavaVFrame extends VFrame {
+
+  private static final String ADDRESS_FORMAT = VM.getVM().isLP64() ? "0x%016x"
+                                                                   : "0x%08x";
+
   /** JVM state */
   public abstract Method getMethod();
   public abstract int    getBCI();
   public abstract StackValueCollection getLocals();
   public abstract StackValueCollection getExpressions();
-  public abstract List   getMonitors();    // List<MonitorInfo>
+  public abstract List<MonitorInfo> getMonitors();
 
   /** Test operation */
   public boolean isJavaFrame() { return true; }
@@ -49,9 +54,112 @@
   // FIXME: not yet implemented
   //  public Address getPendingMonitor(int frameCount);
 
+  public void printLockedObjectClassName(PrintStream tty,
+                                         OopHandle hobj, String lockState) {
+    if (hobj.asLongValue() != 0L) {
+      tty.format("\t- %s <" + ADDRESS_FORMAT + "> ",
+                 lockState, hobj.asLongValue());
+
+      Klass klass = Oop.getKlassForOopHandle(hobj);
+      String klassName = klass.getName().asString();
+      tty.print("(a ");
+      if (klassName.equals("java/lang/Class")) {
+        Oop obj = VM.getVM().getObjectHeap().newOop(hobj);
+        klassName = java_lang_Class.asExternalName(obj);
+        tty.print("java.lang.Class for ");
+      }
+      tty.println(klassName.replace('/', '.') + ")");
+    }
+  }
+
+  private String identifyLockState(MonitorInfo monitor, String waitingState) {
+    Mark mark = new Mark(monitor.owner());
+    if (mark.hasMonitor() &&
+        ( // we have marked ourself as pending on this monitor
+          mark.monitor().equals(thread.getCurrentPendingMonitor()) ||
+          // we are not the owner of this monitor
+          !mark.monitor().isEntered(thread)
+        )) {
+      return waitingState;
+    }
+    return "locked";
+  }
+
   /** Printing used during stack dumps */
-  // FIXME: not yet implemented
-  //  void print_lock_info(int frame_count);
+  public void printLockInfo(PrintStream tty, int frameCount) {
+    // If this is the first frame and it is java.lang.Object.wait(...)
+    // then print out the receiver. Locals are not always available,
+    // e.g., compiled native frames have no scope so there are no locals.
+    if (frameCount == 0) {
+      if (getMethod().getName().asString().equals("wait") &&
+          getMethod().getMethodHolder().getName().asString().equals("java/lang/Object")) {
+        String waitState = "waiting on"; // assume we are waiting
+        // If earlier in the output we reported java.lang.Thread.State ==
+        // "WAITING (on object monitor)" and now we report "waiting on", then
+        // we are still waiting for notification or timeout. Otherwise if
+        // we earlier reported java.lang.Thread.State == "BLOCKED (on object
+        // monitor)", then we are actually waiting to re-lock the monitor.
+        // At this level we can't distinguish the two cases to report
+        // "waited on" rather than "waiting on" for the second case.
+        StackValueCollection locs = getLocals();
+        if (!locs.isEmpty()) {
+          StackValue sv = locs.get(0);
+          if (sv.getType() == BasicType.getTObject()) {
+            OopHandle o = sv.getObject();
+            printLockedObjectClassName(tty, o, waitState);
+          }
+        } else {
+          tty.println("\t- " + waitState + " <no object reference available>");
+        }
+      } else if (thread.getCurrentParkBlocker() != null) {
+        Oop obj = thread.getCurrentParkBlocker();
+        Klass k = obj.getKlass();
+        tty.format("\t- parking to wait for <" + ADDRESS_FORMAT + "> (a %s)",
+                   obj.getHandle().asLongValue(), k.getName().asString());
+        tty.println();
+      }
+    }
+
+    // Print out all monitors that we have locked, or are trying to lock,
+    // including re-locking after being notified or timing out in a wait().
+    List<MonitorInfo> mons = getMonitors();
+    if (!mons.isEmpty()) {
+      boolean foundFirstMonitor = false;
+      for (int index = mons.size() - 1; index >= 0; index--) {
+        MonitorInfo monitor = mons.get(index);
+        if (monitor.eliminated() && isCompiledFrame()) { // Eliminated in compiled code
+          if (monitor.ownerIsScalarReplaced()) {
+            Klass k = Oop.getKlassForOopHandle(monitor.ownerKlass());
+            tty.println("\t- eliminated <owner is scalar replaced> (a " + k.getName().asString() + ")");
+          } else if (monitor.owner() != null) {
+            printLockedObjectClassName(tty, monitor.owner(), "eliminated");
+          }
+          continue;
+        }
+        if (monitor.owner() != null) {
+          // the monitor is associated with an object, i.e., it is locked
+          String lockState = "locked";
+          if (!foundFirstMonitor && frameCount == 0) {
+            // If this is the first frame and we haven't found an owned
+            // monitor before, then we need to see if we have completed
+            // the lock or if we are blocked trying to acquire it. Only
+            // an inflated monitor that is first on the monitor list in
+            // the first frame can block us on a monitor enter.
+            lockState = identifyLockState(monitor, "waiting to lock");
+          } else if (frameCount != 0) {
+            // This is not the first frame so we either own this monitor
+            // or we owned the monitor before and called wait(). Because
+            // wait() could have been called on any monitor in a lower
+            // numbered frame on the stack, we have to check all the
+            // monitors on the list for this frame.
+            lockState = identifyLockState(monitor, "waiting to re-lock in wait()");
+          }
+          printLockedObjectClassName(tty, monitor.owner(), lockState);
+          foundFirstMonitor = true;
+        }
+      }
+    }
+  }
 
   /** Printing operations */
 
@@ -73,22 +181,6 @@
 
     printStackValuesOn(tty, "locals",      getLocals());
     printStackValuesOn(tty, "expressions", getExpressions());
-
-    // List<MonitorInfo>
-    // FIXME: not yet implemented
-    //    List list = getMonitors();
-    //    if (list.isEmpty()) {
-    //      return;
-    //    }
-    //    for (int index = 0; index < list.size(); index++) {
-    //      MonitorInfo monitor = (MonitorInfo) list.get(index);
-    //      tty.print("\t  obj\t");
-    //      monitor.getOwner().printValueOn(tty);
-    //      tty.println();
-    //      tty.print("\t  ");
-    //      monitor.lock().printOn(tty);
-    //      tty.println();
-    //    }
   }
 
   public void printActivation(int index) {
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/StackTrace.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/StackTrace.java	Mon Nov 27 11:20:38 2017 +0530
@@ -76,6 +76,8 @@
                 if (cur.isJavaThread()) {
                     cur.printThreadInfoOn(tty);
                     try {
+                        int count = 0;
+
                         for (JavaVFrame vf = cur.getLastJavaVFrameDbg(); vf != null; vf = vf.javaSender()) {
                             Method method = vf.getMethod();
                             tty.print(" - " + method.externalNameAndSignature() +
@@ -109,6 +111,7 @@
                             }
 
                             tty.println(")");
+                            vf.printLockInfo(tty, count++);
                         }
                     } catch (Exception e) {
                         tty.println("Error occurred during stack walking:");
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/classbrowser/HTMLGenerator.java	Sun Nov 26 09:05:13 2017 -0800
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/classbrowser/HTMLGenerator.java	Mon Nov 27 11:20:38 2017 +0530
@@ -1910,6 +1910,7 @@
       buf.append(thread.getThreadState().toString());
       buf.br();
       buf.beginTag("pre");
+      int count = 0;
       for (JavaVFrame vf = thread.getLastJavaVFrameDbg(); vf != null; vf = vf.javaSender()) {
          Method method = vf.getMethod();
          buf.append(" - ");
@@ -1954,6 +1955,19 @@
          }
          buf.append(")");
          buf.br();
+
+         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+         PrintStream printStream = new PrintStream(bytes);
+         try (printStream) {
+             vf.printLockInfo(printStream, count++);
+             for (String line : bytes.toString().split("\n")) {
+                 if (genHTML) {
+                     line = line.replace("<", "&lt;").replace(">", "&gt;");
+                 }
+                 buf.append(line);
+                 buf.br();
+             }
+         }
       }
 
       buf.endTag("pre");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithLock.java	Mon Nov 27 11:20:38 2017 +0530
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017, 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 jdk.test.lib.apps.LingeredApp;
+
+
+public class LingeredAppWithLock extends LingeredApp {
+
+    public static void lockMethod(Object lock) {
+        synchronized (lock) {
+            try {
+                Thread.sleep(300000);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public static void main(String args[]) {
+        Thread classLock1 = new Thread(() -> lockMethod(LingeredAppWithLock.class));
+        Thread classLock2 = new Thread(() -> lockMethod(LingeredAppWithLock.class));
+        Thread objectLock = new Thread(() -> lockMethod(classLock1));
+        Thread primitiveLock = new Thread(() -> lockMethod(int.class));
+
+        classLock1.start();
+        classLock2.start();
+        objectLock.start();
+        primitiveLock.start();
+
+        LingeredApp.main(args);
+    }
+ }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/sa/TestClhsdbJstackLock.java	Mon Nov 27 11:20:38 2017 +0530
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2017, 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.ArrayList;
+import java.util.Scanner;
+import java.util.List;
+import java.io.File;
+import java.io.IOException;
+import java.util.stream.Collectors;
+import java.io.OutputStream;
+import jdk.test.lib.apps.LingeredApp;
+import jdk.test.lib.JDKToolLauncher;
+import jdk.test.lib.Platform;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.Utils;
+import jdk.test.lib.Asserts;
+
+/*
+ * @test
+ * @library /test/lib
+ * @run main/othervm TestClhsdbJstackLock
+ */
+
+public class TestClhsdbJstackLock {
+
+    private static final String JSTACK_OUT_FILE = "jstack_out.txt";
+
+    private static void verifyJStackOutput() throws Exception {
+
+        Exception unexpected = null;
+        File jstackFile = new File(JSTACK_OUT_FILE);
+        Asserts.assertTrue(jstackFile.exists() && jstackFile.isFile(),
+                           "File with jstack output not created: " +
+                           jstackFile.getAbsolutePath());
+        try {
+            Scanner scanner = new Scanner(jstackFile);
+
+            boolean classLockOwnerFound = false;
+            boolean classLockWaiterFound = false;
+            boolean objectLockOwnerFound = false;
+            boolean primitiveLockOwnerFound = false;
+
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine();
+                System.out.println(line);
+
+                if (line.contains("missing reason for ")) {
+                    unexpected = new RuntimeException("Unexpected msg: missing reason for ");
+                    break;
+                }
+                if (line.matches("^\\s+- locked <0x[0-9a-f]+> \\(a java\\.lang\\.Class for LingeredAppWithLock\\)$")) {
+                    classLockOwnerFound = true;
+                }
+                if (line.matches("^\\s+- waiting to lock <0x[0-9a-f]+> \\(a java\\.lang\\.Class for LingeredAppWithLock\\)$")) {
+                    classLockWaiterFound = true;
+                }
+                if (line.matches("^\\s+- locked <0x[0-9a-f]+> \\(a java\\.lang\\.Thread\\)$")) {
+                    objectLockOwnerFound = true;
+                }
+                if (line.matches("^\\s+- locked <0x[0-9a-f]+> \\(a java\\.lang\\.Class for int\\)$")) {
+                    primitiveLockOwnerFound = true;
+                }
+            }
+
+            if (!classLockOwnerFound || !classLockWaiterFound ||
+                !objectLockOwnerFound || !primitiveLockOwnerFound) {
+                unexpected = new RuntimeException(
+                      "classLockOwnerFound = " + classLockOwnerFound +
+                      ", classLockWaiterFound = " + classLockWaiterFound +
+                      ", objectLockOwnerFound = " + objectLockOwnerFound +
+                      ", primitiveLockOwnerFound = " + primitiveLockOwnerFound);
+            }
+            if (unexpected != null) {
+                throw unexpected;
+            }
+        } catch (Exception ex) {
+            throw new RuntimeException("Test ERROR " + ex, ex);
+        } finally {
+            jstackFile.delete();
+        }
+    }
+
+    private static void startClhsdbForLock(long lingeredAppPid) throws Exception {
+
+        Process p;
+        JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jhsdb");
+        launcher.addToolArg("clhsdb");
+        launcher.addToolArg("--pid");
+        launcher.addToolArg(Long.toString(lingeredAppPid));
+
+        ProcessBuilder pb = new ProcessBuilder();
+        pb.command(launcher.getCommand());
+        System.out.println(pb.command().stream().collect(Collectors.joining(" ")));
+
+        try {
+            p = pb.start();
+        } catch (Exception attachE) {
+            throw new Error("Couldn't start jhsdb or attach to LingeredApp : " + attachE);
+        }
+
+        // Issue the 'jstack' input at the clhsdb prompt.
+        OutputStream input = p.getOutputStream();
+        String str = "jstack > " + JSTACK_OUT_FILE + "\nquit\n";
+        try {
+            input.write(str.getBytes());
+            input.flush();
+        } catch (IOException ioe) {
+            throw new Error("Problem issuing the jstack command: " + str, ioe);
+        }
+
+        try {
+            p.waitFor();
+        } catch (InterruptedException ie) {
+            throw new Error("Problem awaiting the child process: " + ie, ie);
+        }
+
+        int exitValue = p.exitValue();
+        if (exitValue != 0) {
+            String output;
+            try {
+                output = new OutputAnalyzer(p).getOutput();
+            } catch (IOException ioe) {
+                throw new Error("Can't get failed clhsdb process output: " + ioe, ioe);
+            }
+            throw new AssertionError("clhsdb wasn't run successfully: " + output);
+        }
+    }
+
+    public static void main (String... args) throws Exception {
+
+        LingeredApp app = null;
+
+        if (!Platform.shouldSAAttach()) {
+            System.out.println("SA attach not expected to work - test skipped.");
+            return;
+        }
+
+        try {
+            List<String> vmArgs = new ArrayList<String>(Utils.getVmOptions());
+
+            app = new LingeredAppWithLock();
+            LingeredApp.startApp(vmArgs, app);
+            System.out.println ("Started LingeredApp with pid " + app.getPid());
+            startClhsdbForLock(app.getPid());
+            verifyJStackOutput();
+        } finally {
+            LingeredApp.stopApp(app);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackLock.java	Mon Nov 27 11:20:38 2017 +0530
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2017, 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.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import jdk.test.lib.apps.LingeredApp;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.JDKToolLauncher;
+import jdk.test.lib.Platform;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Utils;
+
+/*
+ * @test
+ * @library /test/lib
+ * @run main/othervm TestJhsdbJstackLock
+ */
+
+public class TestJhsdbJstackLock {
+
+    public static void main (String... args) throws Exception {
+
+        LingeredApp app = null;
+
+        if (!Platform.shouldSAAttach()) {
+            System.out.println("SA attach not expected to work - test skipped.");
+            return;
+        }
+
+        try {
+            List<String> vmArgs = new ArrayList<String>(Utils.getVmOptions());
+
+            app = new LingeredAppWithLock();
+            LingeredApp.startApp(vmArgs, app);
+            System.out.println ("Started LingeredApp with pid " + app.getPid());
+
+            JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jhsdb");
+            launcher.addToolArg("jstack");
+            launcher.addToolArg("--pid");
+            launcher.addToolArg(Long.toString(app.getPid()));
+
+            ProcessBuilder pb = new ProcessBuilder();
+            pb.command(launcher.getCommand());
+            Process jhsdb = pb.start();
+
+            jhsdb.waitFor();
+
+            OutputAnalyzer out = new OutputAnalyzer(jhsdb);
+            System.out.println(out.getStdout());
+            System.err.println(out.getStderr());
+
+            out.shouldMatch("^\\s+- locked <0x[0-9a-f]+> \\(a java\\.lang\\.Class for LingeredAppWithLock\\)$");
+            out.shouldMatch("^\\s+- waiting to lock <0x[0-9a-f]+> \\(a java\\.lang\\.Class for LingeredAppWithLock\\)$");
+            out.shouldMatch("^\\s+- locked <0x[0-9a-f]+> \\(a java\\.lang\\.Thread\\)$");
+            out.shouldMatch("^\\s+- locked <0x[0-9a-f]+> \\(a java\\.lang\\.Class for int\\)$");
+            out.stderrShouldBeEmpty();
+
+            System.out.println("Test Completed");
+        } finally {
+            LingeredApp.stopApp(app);
+        }
+    }
+}