001/* 002 * This file is part of the Jikes RVM project (http://jikesrvm.org). 003 * 004 * This file is licensed to You under the Eclipse Public License (EPL); 005 * You may not use this file except in compliance with the License. You 006 * may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/eclipse-1.0.php 009 * 010 * See the COPYRIGHT.txt file distributed with this work for information 011 * regarding copyright ownership. 012 */ 013package org.jikesrvm.scheduler; 014 015import static org.jikesrvm.objectmodel.ThinLockConstants.TL_LOCK_ID_MASK; 016import static org.jikesrvm.objectmodel.ThinLockConstants.TL_LOCK_ID_SHIFT; 017import static org.jikesrvm.objectmodel.ThinLockConstants.TL_THREAD_ID_SHIFT; 018 019import org.jikesrvm.VM; 020import org.jikesrvm.objectmodel.ObjectModel; 021import org.jikesrvm.runtime.Callbacks; 022import org.jikesrvm.runtime.Magic; 023import org.jikesrvm.util.Services; 024import org.vmmagic.pragma.Inline; 025import org.vmmagic.pragma.Interruptible; 026import org.vmmagic.pragma.Uninterruptible; 027import org.vmmagic.pragma.Unpreemptible; 028import org.vmmagic.pragma.UnpreemptibleNoWarn; 029import org.vmmagic.unboxed.Offset; 030import org.vmmagic.unboxed.Word; 031 032/** 033 Lock provides RVM support for monitors and Java level 034 synchronization. 035 036 <p> 037 This class may be decomposed into four sections: 038 <OL> 039 <LI> support for synchronization methods of java.lang.Object, 040 <LI> heavy weight locking mechanism, 041 <LI> management of heavy weight locks, and 042 <LI> debugging and performance tuning support. 043 </OL> 044 045 <p><STRONG>Requirement 1:</STRONG> 046 It must be possible to lock an object when allocations are not 047 allowed. 048 </p> 049 050 <p><STRONG>Requirement 2:</STRONG> 051 After a lock has been obtained, the code of this class must return 052 without allowing a thread switch. The exception handler 053 of the baseline compiler assumes that until lock() returns the lock 054 has not been obtained.) 055 </p> 056 057 <p><STRONG>Section 1:</STRONG> 058 support for {@link java.lang.Object#notify}, {@link 059java.lang.Object#notifyAll}, and {@link java.lang.Object#wait()}. 060 When these methods are called, the indicated object must be locked 061 by the current thread. </p> 062 063 <p><STRONG>Section 2:</STRONG> 064 has two sections. <EM>Section 2a:</EM> locks (and unlocking) 065 objects with heavy-weight locks associated with them. <EM>Section 066 2b:</EM> associates (and disassociates) heavy-weight locks with 067 objects. 068 </p> 069 070 <p><STRONG>Section 3:</STRONG> 071 Allocates (and frees) heavy weight locks consistent with Requirement 072 1. 073 </p> 074 075 <p><STRONG>Section 4:</STRONG> 076 debugging and performance tuning stuff. 077 </p> 078 079 <p> 080 The following performance tuning issues have not yet been addressed 081 adaquately:</p> 082 <OL> 083 <LI> <EM>What to do if the attempt to lock an object fails?</EM> There 084 are three choices: try again (busy-wait), yield and then try again, 085 inflate the lock and yield to the heavy-weight lock's entering 086 queue. Currently, yield n times, then inflate. 087 (This seemed to be best for the portBOB benchmark on a 12-way AIX 088 SMP in the Fall of '99.) 089 <LI> <EM>When should a heavy-weight lock be deflated?</EM> Currently, 090 deflation happens when the lock is unlocked with nothing on either 091 of its queues. Probably better, would be to periodically (what 092 period?) examine heavy-weight locks and deflate any that havn't 093 been held for a while (how long?). 094 <LI> <EM>How many heavy-weight locks are needed? and how should they be 095 managed?</EM> Currently, each processor maintains a pool of free 096 locks. When a lock is inflated by a processor it is taken from 097 this pool and when a lock is deflated by a processor it gets added 098 to the processors pool. Since inflation can happen on one processor 099 and deflation on another, this can create an imbalance. It might 100 be worth investigating a scheme for balancing these local pools. 101 <LI> <EM>Is there any advantage to using the {@link SpinLock#tryLock} 102 method?</EM> 103 </OL> 104 <p> 105 Once these questions, and the issue of using MCS locking in {@link SpinLock}, 106 have been investigated, then a larger performance issue 107 comes into view. A number of different light-weight locking schemes have 108 been proposed over the years (see last several OOPSLA's). It should be 109 possible to implement each of them in RVM and compare their performance. 110 </p> 111 112 @see java.lang.Object 113 @see ThinLock 114 @see SpinLock 115 */ 116 117@Uninterruptible 118public final class Lock { 119 /**************************************************************************** 120 * Constants 121 */ 122 123 /** do debug tracing? */ 124 protected static final boolean trace = false; 125 /** Control the gathering of statistics */ 126 public static final boolean STATS = false; 127 128 /** The (fixed) number of entries in the lock table spine */ 129 protected static final int LOCK_SPINE_SIZE = 128; 130 /** The log size of each chunk in the spine */ 131 protected static final int LOG_LOCK_CHUNK_SIZE = 11; 132 /** The size of each chunk in the spine */ 133 protected static final int LOCK_CHUNK_SIZE = 1 << LOG_LOCK_CHUNK_SIZE; 134 /** The mask used to get the chunk-level index */ 135 protected static final int LOCK_CHUNK_MASK = LOCK_CHUNK_SIZE - 1; 136 /** The maximum possible number of locks */ 137 protected static final int MAX_LOCKS = LOCK_SPINE_SIZE * LOCK_CHUNK_SIZE; 138 /** The number of chunks to allocate on startup */ 139 protected static final int INITIAL_CHUNKS = 1; 140 141 /** 142 * Should we give up or persist in the attempt to get a heavy-weight lock, 143 * if its <code>mutex</code> microlock is held by another procesor. 144 */ 145 private static final boolean tentativeMicrolocking = false; 146 147 // Heavy lock table. 148 149 /** The table of locks. */ 150 private static Lock[][] locks; 151 /** Used during allocation of locks within the table. */ 152 private static final SpinLock lockAllocationMutex = new SpinLock(); 153 /** The number of chunks in the spine that have been physically allocated */ 154 private static int chunksAllocated; 155 /** The number of locks allocated (these may either be in use, on a global 156 * freelist, or on a thread's freelist. */ 157 private static int nextLockIndex; 158 159 // Global free list. 160 161 /** A global lock free list head */ 162 private static Lock globalFreeLock; 163 /** the number of locks held on the global free list. */ 164 private static int globalFreeLocks; 165 /** the total number of allocation operations. */ 166 private static int globalLocksAllocated; 167 /** the total number of free operations. */ 168 private static int globalLocksFreed; 169 170 // Statistics 171 172 /** Number of lock operations */ 173 public static int lockOperations; 174 /** Number of unlock operations */ 175 public static int unlockOperations; 176 /** Number of deflations */ 177 public static int deflations; 178 179 /**************************************************************************** 180 * Instance 181 */ 182 183 /** The object being locked (if any). */ 184 protected Object lockedObject; 185 /** The id of the thread that owns this lock (if any). */ 186 protected int ownerId; 187 /** The number of times the owning thread (if any) has acquired this lock. */ 188 protected int recursionCount; 189 /** A spin lock to handle contention for the data structures of this lock. */ 190 public final SpinLock mutex; 191 /** Is this lock currently being used? */ 192 protected boolean active; 193 /** The next free lock on the free lock list */ 194 private Lock nextFreeLock; 195 /** This lock's index in the lock table*/ 196 protected int index; 197 /** Queue for entering the lock, guarded by mutex. */ 198 ThreadQueue entering; 199 /** Queue for waiting on a notify, guarded by mutex as well. */ 200 ThreadQueue waiting; 201 202 /** 203 * A heavy weight lock to handle extreme contention and wait/notify 204 * synchronization. 205 */ 206 public Lock() { 207 mutex = new SpinLock(); 208 entering = new ThreadQueue(); 209 waiting = new ThreadQueue(); 210 } 211 212 /** 213 * Acquires this heavy-weight lock on the indicated object. 214 * 215 * @param o the object to be locked 216 * @return true, if the lock succeeds; false, otherwise 217 */ 218 @Unpreemptible 219 public boolean lockHeavy(Object o) { 220 if (tentativeMicrolocking) { 221 if (!mutex.tryLock()) { 222 return false; 223 } 224 } else { 225 mutex.lock(); // Note: thread switching is not allowed while mutex is held. 226 } 227 return lockHeavyLocked(o); 228 } 229 230 /** 231 * Completes the task of acquiring the heavy lock, assuming that the mutex 232 is already acquired (locked). 233 * @param o the object whose lock is to be acquired 234 * @return whether locking succeeded 235 */ 236 @Unpreemptible 237 public boolean lockHeavyLocked(Object o) { 238 if (lockedObject != o) { // lock disappeared before we got here 239 mutex.unlock(); // thread switching benign 240 return false; 241 } 242 if (STATS) lockOperations++; 243 RVMThread me = RVMThread.getCurrentThread(); 244 int threadId = me.getLockingId(); 245 if (ownerId == threadId) { 246 recursionCount++; 247 } else if (ownerId == 0) { 248 ownerId = threadId; 249 recursionCount = 1; 250 } else { 251 entering.enqueue(me); 252 mutex.unlock(); 253 me.monitor().lockNoHandshake(); 254 while (entering.isQueued(me)) { 255 me.monitor().waitWithHandshake(); // this may spuriously return 256 } 257 me.monitor().unlock(); 258 return false; 259 } 260 mutex.unlock(); // thread-switching benign 261 return true; 262 } 263 264 @UnpreemptibleNoWarn 265 private static void raiseIllegalMonitorStateException(String msg, Object o) { 266 throw new IllegalMonitorStateException(msg + o); 267 } 268 269 /** 270 * Releases this heavy-weight lock on the indicated object. 271 * 272 * @param o the object to be unlocked 273 */ 274 @Unpreemptible 275 public void unlockHeavy(Object o) { 276 boolean deflated = false; 277 mutex.lock(); // Note: thread switching is not allowed while mutex is held. 278 RVMThread me = RVMThread.getCurrentThread(); 279 if (ownerId != me.getLockingId()) { 280 mutex.unlock(); // thread-switching benign 281 raiseIllegalMonitorStateException("heavy unlocking", o); 282 } 283 recursionCount--; 284 if (0 < recursionCount) { 285 mutex.unlock(); // thread-switching benign 286 return; 287 } 288 if (STATS) unlockOperations++; 289 ownerId = 0; 290 RVMThread toAwaken = entering.dequeue(); 291 if (toAwaken == null && entering.isEmpty() && waiting.isEmpty()) { // heavy lock can be deflated 292 // Possible project: decide on a heuristic to control when lock should be deflated 293 Offset lockOffset = Magic.getObjectType(o).getThinLockOffset(); 294 if (!lockOffset.isMax()) { // deflate heavy lock 295 deflate(o, lockOffset); 296 deflated = true; 297 } 298 } 299 mutex.unlock(); // does a Magic.sync(); (thread-switching benign) 300 if (toAwaken != null) { 301 toAwaken.monitor().lockedBroadcastNoHandshake(); 302 } 303 } 304 305 /** 306 * Disassociates this heavy-weight lock from the indicated object. 307 * This lock is not held, nor are any threads on its queues. Note: 308 * the mutex for this lock is held when deflate is called. 309 * 310 * @param o the object from which this lock is to be disassociated 311 * @param lockOffset the lock's offset from the object 312 */ 313 private void deflate(Object o, Offset lockOffset) { 314 if (VM.VerifyAssertions) { 315 VM._assert(lockedObject == o); 316 VM._assert(recursionCount == 0); 317 VM._assert(entering.isEmpty()); 318 VM._assert(waiting.isEmpty()); 319 } 320 if (STATS) deflations++; 321 ThinLock.markDeflated(o, lockOffset, index); 322 lockedObject = null; 323 free(this); 324 } 325 326 /** 327 * Set the owner of a lock 328 * @param id The thread id of the owner. 329 */ 330 public void setOwnerId(int id) { 331 ownerId = id; 332 } 333 334 /** 335 * @return the thread id of the current owner of the lock. 336 */ 337 public int getOwnerId() { 338 return ownerId; 339 } 340 341 /** 342 * Update the lock's recursion count. 343 * 344 * @param c new recursion count 345 */ 346 public void setRecursionCount(int c) { 347 recursionCount = c; 348 } 349 350 /** 351 * @return the lock's recursion count. 352 */ 353 public int getRecursionCount() { 354 return recursionCount; 355 } 356 357 /** 358 * Set the object that this lock is referring to. 359 * 360 * @param o the new locked object 361 */ 362 public void setLockedObject(Object o) { 363 lockedObject = o; 364 } 365 366 /** 367 * @return the object that this lock is referring to. 368 */ 369 public Object getLockedObject() { 370 return lockedObject; 371 } 372 373 /** 374 * Dump threads blocked trying to get this lock 375 */ 376 protected void dumpBlockedThreads() { 377 VM.sysWrite(" entering: "); 378 entering.dump(); 379 } 380 /** 381 * Dump threads waiting to be notified on this lock 382 */ 383 protected void dumpWaitingThreads() { 384 VM.sysWrite(" waiting: "); 385 waiting.dump(); 386 } 387 388 /** 389 * Reports the state of a heavy-weight lock, via {@link VM#sysWrite}. 390 */ 391 private void dump() { 392 if (!active) { 393 return; 394 } 395 VM.sysWrite("Lock "); 396 VM.sysWriteInt(index); 397 VM.sysWrite(":\n"); 398 VM.sysWrite(" lockedObject: "); 399 VM.sysWriteHex(Magic.objectAsAddress(lockedObject)); 400 VM.sysWrite(" thin lock = "); 401 VM.sysWriteHex(Magic.objectAsAddress(lockedObject).loadAddress(ObjectModel.defaultThinLockOffset())); 402 VM.sysWrite(" object type = "); 403 VM.sysWrite(Magic.getObjectType(lockedObject).getDescriptor()); 404 VM.sysWriteln(); 405 406 VM.sysWrite(" ownerId: "); 407 VM.sysWriteInt(ownerId); 408 VM.sysWrite(" ("); 409 VM.sysWriteInt(ownerId >>> TL_THREAD_ID_SHIFT); 410 VM.sysWrite(") recursionCount: "); 411 VM.sysWriteInt(recursionCount); 412 VM.sysWriteln(); 413 dumpBlockedThreads(); 414 dumpWaitingThreads(); 415 416 VM.sysWrite(" mutexLatestContender: "); 417 if (mutex.latestContender == null) { 418 VM.sysWrite("<null>"); 419 } else { 420 VM.sysWriteInt(mutex.latestContender.getThreadSlot()); 421 } 422 VM.sysWrite("\n"); 423 } 424 425 /** 426 * @param t a thread 427 * @return whether this lock is blocking the given thread 428 */ 429 protected boolean isBlocked(RVMThread t) { 430 return entering.isQueued(t); 431 } 432 433 /** 434 * @param t a thread 435 * @return whether the thread is waiting on this lock 436 */ 437 protected boolean isWaiting(RVMThread t) { 438 return waiting.isQueued(t); 439 } 440 441 /**************************************************************************** 442 * Static Lock Table 443 */ 444 445 /** 446 * Sets up the data structures for holding heavy-weight locks. 447 */ 448 @Interruptible 449 public static void init() { 450 nextLockIndex = 1; 451 locks = new Lock[LOCK_SPINE_SIZE][]; 452 for (int i = 0; i < INITIAL_CHUNKS; i++) { 453 chunksAllocated++; 454 locks[i] = new Lock[LOCK_CHUNK_SIZE]; 455 } 456 if (VM.VerifyAssertions) { 457 // check that each potential lock is addressable 458 VM._assert(((MAX_LOCKS - 1) <= 459 TL_LOCK_ID_MASK.rshl(TL_LOCK_ID_SHIFT).toInt()) || 460 TL_LOCK_ID_MASK.EQ(Word.fromIntSignExtend(-1))); 461 } 462 } 463 464 /** 465 * Delivers up an unassigned heavy-weight lock. Locks are allocated 466 * from processor specific regions or lists, so normally no synchronization 467 * is required to obtain a lock. 468 * <p> 469 * Collector threads cannot use heavy-weight locks. 470 * 471 * @return a free Lock; or <code>null</code>, if garbage collection is not enabled 472 */ 473 @UnpreemptibleNoWarn("The caller is prepared to lose control when it allocates a lock") 474 static Lock allocate() { 475 RVMThread me = RVMThread.getCurrentThread(); 476 if (me.cachedFreeLock != null) { 477 Lock l = me.cachedFreeLock; 478 me.cachedFreeLock = null; 479 if (trace) { 480 VM.sysWriteln("Lock.allocate: returning ",Magic.objectAsAddress(l), 481 ", a cached free lock from Thread #",me.getThreadSlot()); 482 } 483 l.active = true; 484 return l; 485 } 486 487 Lock l = null; 488 while (l == null) { 489 if (globalFreeLock != null) { 490 lockAllocationMutex.lock(); 491 l = globalFreeLock; 492 if (l != null) { 493 globalFreeLock = l.nextFreeLock; 494 l.nextFreeLock = null; 495 l.active = true; 496 globalFreeLocks--; 497 } 498 lockAllocationMutex.unlock(); 499 if (trace && l != null) { 500 VM.sysWriteln("Lock.allocate: returning ",Magic.objectAsAddress(l), 501 " from the global freelist for Thread #",me.getThreadSlot()); 502 } 503 } else { 504 l = new Lock(); // may cause thread switch (and processor loss) 505 lockAllocationMutex.lock(); 506 if (globalFreeLock == null) { 507 // ok, it's still correct for us to be adding a new lock 508 if (nextLockIndex >= MAX_LOCKS) { 509 VM.sysWriteln("Too many fat locks"); // make MAX_LOCKS bigger? we can keep going?? 510 VM.sysFail("Exiting VM with fatal error"); 511 } 512 l.index = nextLockIndex++; 513 globalLocksAllocated++; 514 } else { 515 l = null; // someone added to the freelist, try again 516 } 517 lockAllocationMutex.unlock(); 518 if (l != null) { 519 if (l.index >= numLocks()) { 520 /* We need to grow the table */ 521 growLocks(l.index); 522 } 523 addLock(l); 524 l.active = true; 525 /* make sure other processors see lock initialization. 526 * Note: Derek and I BELIEVE that an isync is not required in the other processor because the lock is newly allocated - Bowen */ 527 Magic.sync(); 528 } 529 if (trace && l != null) { 530 VM.sysWriteln("Lock.allocate: returning ",Magic.objectAsAddress(l), 531 ", a freshly allocated lock for Thread #", 532 me.getThreadSlot()); 533 } 534 } 535 } 536 return l; 537 } 538 539 /** 540 * Recycles an unused heavy-weight lock. Locks are deallocated 541 * to processor specific lists, so normally no synchronization 542 * is required to obtain or release a lock. 543 * 544 * @param l the unused lock 545 */ 546 protected static void free(Lock l) { 547 l.active = false; 548 RVMThread me = RVMThread.getCurrentThread(); 549 if (me.cachedFreeLock == null) { 550 if (trace) { 551 VM.sysWriteln("Lock.free: setting ",Magic.objectAsAddress(l), 552 " as the cached free lock for Thread #", 553 me.getThreadSlot()); 554 } 555 me.cachedFreeLock = l; 556 } else { 557 if (trace) { 558 VM.sysWriteln("Lock.free: returning ",Magic.objectAsAddress(l), 559 " to the global freelist for Thread #", 560 me.getThreadSlot()); 561 } 562 returnLock(l); 563 } 564 } 565 static void returnLock(Lock l) { 566 if (trace) { 567 VM.sysWriteln("Lock.returnLock: returning ",Magic.objectAsAddress(l), 568 " to the global freelist for Thread #", 569 RVMThread.getCurrentThreadSlot()); 570 } 571 lockAllocationMutex.lock(); 572 l.nextFreeLock = globalFreeLock; 573 globalFreeLock = l; 574 globalFreeLocks++; 575 globalLocksFreed++; 576 lockAllocationMutex.unlock(); 577 } 578 579 /** 580 * Grow the locks table by allocating a new spine chunk. 581 * 582 * @param id the lock's index in the table 583 */ 584 @UnpreemptibleNoWarn("The caller is prepared to lose control when it allocates a lock") 585 static void growLocks(int id) { 586 int spineId = id >> LOG_LOCK_CHUNK_SIZE; 587 if (spineId >= LOCK_SPINE_SIZE) { 588 VM.sysFail("Cannot grow lock array greater than maximum possible index"); 589 } 590 for (int i = chunksAllocated; i <= spineId; i++) { 591 if (locks[i] != null) { 592 /* We were beaten to it */ 593 continue; 594 } 595 596 /* Allocate the chunk */ 597 Lock[] newChunk = new Lock[LOCK_CHUNK_SIZE]; 598 599 lockAllocationMutex.lock(); 600 if (locks[i] == null) { 601 /* We got here first */ 602 locks[i] = newChunk; 603 chunksAllocated++; 604 } 605 lockAllocationMutex.unlock(); 606 } 607 } 608 609 /** 610 * @return the number of lock slots that have been allocated. This provides 611 * the range of valid lock ids. 612 */ 613 public static int numLocks() { 614 return chunksAllocated * LOCK_CHUNK_SIZE; 615 } 616 617 /** 618 * Read a lock from the lock table by id. 619 * 620 * @param id The lock id 621 * @return The lock object. 622 */ 623 @Inline 624 public static Lock getLock(int id) { 625 return locks[id >> LOG_LOCK_CHUNK_SIZE][id & LOCK_CHUNK_MASK]; 626 } 627 628 /** 629 * Add a lock to the lock table 630 * 631 * @param l The lock object 632 */ 633 @Uninterruptible 634 public static void addLock(Lock l) { 635 Lock[] chunk = locks[l.index >> LOG_LOCK_CHUNK_SIZE]; 636 int index = l.index & LOCK_CHUNK_MASK; 637 Services.setArrayUninterruptible(chunk, index, l); 638 } 639 640 /** 641 * Dump the lock table. 642 */ 643 public static void dumpLocks() { 644 for (int i = 0; i < numLocks(); i++) { 645 Lock l = getLock(i); 646 if (l != null) { 647 l.dump(); 648 } 649 } 650 VM.sysWrite("\n"); 651 VM.sysWrite("lock availability stats: "); 652 VM.sysWriteInt(globalLocksAllocated); 653 VM.sysWrite(" locks allocated, "); 654 VM.sysWriteInt(globalLocksFreed); 655 VM.sysWrite(" locks freed, "); 656 VM.sysWriteInt(globalFreeLocks); 657 VM.sysWrite(" free locks\n"); 658 } 659 660 /** 661 * Count number of locks held by thread 662 * @param id the thread locking ID we're counting for 663 * @return number of locks held 664 */ 665 public static int countLocksHeldByThread(int id) { 666 int count = 0; 667 for (int i = 0; i < numLocks(); i++) { 668 Lock l = getLock(i); 669 if (l != null && l.active && l.ownerId == id && l.recursionCount > 0) { 670 count++; 671 } 672 } 673 return count; 674 } 675 676 /** 677 * scan lock queues for thread and report its state 678 * @param t the thread whose state is of interest 679 * @return the thread state in human readable form 680 */ 681 @Interruptible 682 public static String getThreadState(RVMThread t) { 683 for (int i = 0; i < numLocks(); i++) { 684 Lock l = getLock(i); 685 if (l == null || !l.active) continue; 686 if (l.isBlocked(t)) return ("waitingForLock(blocked)" + i); 687 if (l.isWaiting(t)) return "waitingForNotification(waiting)"; 688 } 689 return null; 690 } 691 692 /**************************************************************************** 693 * Statistics 694 */ 695 696 /** 697 * Set up callbacks to report statistics. 698 */ 699 @Interruptible 700 public static void boot() { 701 if (STATS) { 702 Callbacks.addExitMonitor(new Lock.ExitMonitor()); 703 Callbacks.addAppRunStartMonitor(new Lock.AppRunStartMonitor()); 704 } 705 } 706 707 /** 708 * Initialize counts in preparation for gathering statistics 709 */ 710 private static final class AppRunStartMonitor implements Callbacks.AppRunStartMonitor { 711 @Override 712 public void notifyAppRunStart(String app, int value) { 713 lockOperations = 0; 714 unlockOperations = 0; 715 deflations = 0; 716 717 ThinLock.notifyAppRunStart("", 0); 718 } 719 } 720 721 /** 722 * Report statistics at the end of execution. 723 */ 724 private static final class ExitMonitor implements Callbacks.ExitMonitor { 725 @Override 726 public void notifyExit(int value) { 727 int totalLocks = lockOperations + ThinLock.fastLocks + ThinLock.slowLocks; 728 729 RVMThread.dumpStats(); 730 VM.sysWrite(" notifyAll operations\n"); 731 VM.sysWrite("FatLocks: "); 732 VM.sysWrite(lockOperations); 733 VM.sysWrite(" locks"); 734 Services.percentage(lockOperations, totalLocks, "all lock operations"); 735 VM.sysWrite("FatLocks: "); 736 VM.sysWrite(unlockOperations); 737 VM.sysWrite(" unlock operations\n"); 738 VM.sysWrite("FatLocks: "); 739 VM.sysWrite(deflations); 740 VM.sysWrite(" deflations\n"); 741 742 ThinLock.notifyExit(totalLocks); 743 VM.sysWriteln(); 744 745 VM.sysWrite("lock availability stats: "); 746 VM.sysWriteInt(globalLocksAllocated); 747 VM.sysWrite(" locks allocated, "); 748 VM.sysWriteInt(globalLocksFreed); 749 VM.sysWrite(" locks freed, "); 750 VM.sysWriteInt(globalFreeLocks); 751 VM.sysWrite(" free locks\n"); 752 } 753 } 754} 755