Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/hotspot/share/runtime/jniHandles.cpp
40951 views
1
/*
2
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation.
8
*
9
* This code is distributed in the hope that it will be useful, but WITHOUT
10
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12
* version 2 for more details (a copy is included in the LICENSE file that
13
* accompanied this code).
14
*
15
* You should have received a copy of the GNU General Public License version
16
* 2 along with this work; if not, write to the Free Software Foundation,
17
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
*
19
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20
* or visit www.oracle.com if you need additional information or have any
21
* questions.
22
*
23
*/
24
25
#include "precompiled.hpp"
26
#include "gc/shared/collectedHeap.hpp"
27
#include "gc/shared/oopStorage.inline.hpp"
28
#include "gc/shared/oopStorageSet.hpp"
29
#include "logging/log.hpp"
30
#include "memory/iterator.hpp"
31
#include "memory/universe.hpp"
32
#include "oops/access.inline.hpp"
33
#include "oops/oop.inline.hpp"
34
#include "runtime/handles.inline.hpp"
35
#include "runtime/jniHandles.inline.hpp"
36
#include "runtime/mutexLocker.hpp"
37
#include "runtime/thread.inline.hpp"
38
#include "utilities/align.hpp"
39
#include "utilities/debug.hpp"
40
41
OopStorage* JNIHandles::global_handles() {
42
return _global_handles;
43
}
44
45
OopStorage* JNIHandles::weak_global_handles() {
46
return _weak_global_handles;
47
}
48
49
// Serviceability agent support.
50
OopStorage* JNIHandles::_global_handles = NULL;
51
OopStorage* JNIHandles::_weak_global_handles = NULL;
52
53
void jni_handles_init() {
54
JNIHandles::_global_handles = OopStorageSet::create_strong("JNI Global", mtInternal);
55
JNIHandles::_weak_global_handles = OopStorageSet::create_weak("JNI Weak", mtInternal);
56
}
57
58
jobject JNIHandles::make_local(oop obj) {
59
return make_local(Thread::current(), obj);
60
}
61
62
// Used by NewLocalRef which requires NULL on out-of-memory
63
jobject JNIHandles::make_local(Thread* thread, oop obj, AllocFailType alloc_failmode) {
64
if (obj == NULL) {
65
return NULL; // ignore null handles
66
} else {
67
assert(oopDesc::is_oop(obj), "not an oop");
68
assert(thread->is_Java_thread(), "not a Java thread");
69
assert(!current_thread_in_native(), "must not be in native");
70
return thread->active_handles()->allocate_handle(obj, alloc_failmode);
71
}
72
}
73
74
static void report_handle_allocation_failure(AllocFailType alloc_failmode,
75
const char* handle_kind) {
76
if (alloc_failmode == AllocFailStrategy::EXIT_OOM) {
77
// Fake size value, since we don't know the min allocation size here.
78
vm_exit_out_of_memory(sizeof(oop), OOM_MALLOC_ERROR,
79
"Cannot create %s JNI handle", handle_kind);
80
} else {
81
assert(alloc_failmode == AllocFailStrategy::RETURN_NULL, "invariant");
82
}
83
}
84
85
jobject JNIHandles::make_global(Handle obj, AllocFailType alloc_failmode) {
86
assert(!Universe::heap()->is_gc_active(), "can't extend the root set during GC");
87
assert(!current_thread_in_native(), "must not be in native");
88
jobject res = NULL;
89
if (!obj.is_null()) {
90
// ignore null handles
91
assert(oopDesc::is_oop(obj()), "not an oop");
92
oop* ptr = global_handles()->allocate();
93
// Return NULL on allocation failure.
94
if (ptr != NULL) {
95
assert(*ptr == NULL, "invariant");
96
NativeAccess<>::oop_store(ptr, obj());
97
res = reinterpret_cast<jobject>(ptr);
98
} else {
99
report_handle_allocation_failure(alloc_failmode, "global");
100
}
101
}
102
103
return res;
104
}
105
106
jobject JNIHandles::make_weak_global(Handle obj, AllocFailType alloc_failmode) {
107
assert(!Universe::heap()->is_gc_active(), "can't extend the root set during GC");
108
assert(!current_thread_in_native(), "must not be in native");
109
jobject res = NULL;
110
if (!obj.is_null()) {
111
// ignore null handles
112
assert(oopDesc::is_oop(obj()), "not an oop");
113
oop* ptr = weak_global_handles()->allocate();
114
// Return NULL on allocation failure.
115
if (ptr != NULL) {
116
assert(*ptr == NULL, "invariant");
117
NativeAccess<ON_PHANTOM_OOP_REF>::oop_store(ptr, obj());
118
char* tptr = reinterpret_cast<char*>(ptr) + weak_tag_value;
119
res = reinterpret_cast<jobject>(tptr);
120
} else {
121
report_handle_allocation_failure(alloc_failmode, "weak global");
122
}
123
}
124
return res;
125
}
126
127
// Resolve some erroneous cases to NULL, rather than treating them as
128
// possibly unchecked errors. In particular, deleted handles are
129
// treated as NULL (though a deleted and later reallocated handle
130
// isn't detected).
131
oop JNIHandles::resolve_external_guard(jobject handle) {
132
oop result = NULL;
133
if (handle != NULL) {
134
result = resolve_impl<DECORATORS_NONE, true /* external_guard */>(handle);
135
}
136
return result;
137
}
138
139
bool JNIHandles::is_global_weak_cleared(jweak handle) {
140
assert(handle != NULL, "precondition");
141
assert(is_jweak(handle), "not a weak handle");
142
oop* oop_ptr = jweak_ptr(handle);
143
oop value = NativeAccess<ON_PHANTOM_OOP_REF | AS_NO_KEEPALIVE>::oop_load(oop_ptr);
144
return value == NULL;
145
}
146
147
void JNIHandles::destroy_global(jobject handle) {
148
if (handle != NULL) {
149
assert(!is_jweak(handle), "wrong method for detroying jweak");
150
oop* oop_ptr = jobject_ptr(handle);
151
NativeAccess<>::oop_store(oop_ptr, (oop)NULL);
152
global_handles()->release(oop_ptr);
153
}
154
}
155
156
157
void JNIHandles::destroy_weak_global(jobject handle) {
158
if (handle != NULL) {
159
assert(is_jweak(handle), "JNI handle not jweak");
160
oop* oop_ptr = jweak_ptr(handle);
161
NativeAccess<ON_PHANTOM_OOP_REF>::oop_store(oop_ptr, (oop)NULL);
162
weak_global_handles()->release(oop_ptr);
163
}
164
}
165
166
167
void JNIHandles::oops_do(OopClosure* f) {
168
global_handles()->oops_do(f);
169
}
170
171
172
void JNIHandles::weak_oops_do(BoolObjectClosure* is_alive, OopClosure* f) {
173
weak_global_handles()->weak_oops_do(is_alive, f);
174
}
175
176
177
void JNIHandles::weak_oops_do(OopClosure* f) {
178
weak_global_handles()->weak_oops_do(f);
179
}
180
181
bool JNIHandles::is_global_storage(const OopStorage* storage) {
182
return _global_handles == storage;
183
}
184
185
inline bool is_storage_handle(const OopStorage* storage, const oop* ptr) {
186
return storage->allocation_status(ptr) == OopStorage::ALLOCATED_ENTRY;
187
}
188
189
190
jobjectRefType JNIHandles::handle_type(Thread* thread, jobject handle) {
191
assert(handle != NULL, "precondition");
192
jobjectRefType result = JNIInvalidRefType;
193
if (is_jweak(handle)) {
194
if (is_storage_handle(weak_global_handles(), jweak_ptr(handle))) {
195
result = JNIWeakGlobalRefType;
196
}
197
} else {
198
switch (global_handles()->allocation_status(jobject_ptr(handle))) {
199
case OopStorage::ALLOCATED_ENTRY:
200
result = JNIGlobalRefType;
201
break;
202
203
case OopStorage::UNALLOCATED_ENTRY:
204
break; // Invalid global handle
205
206
case OopStorage::INVALID_ENTRY:
207
// Not in global storage. Might be a local handle.
208
if (is_local_handle(thread, handle) ||
209
(thread->is_Java_thread() &&
210
is_frame_handle(thread->as_Java_thread(), handle))) {
211
result = JNILocalRefType;
212
}
213
break;
214
215
default:
216
ShouldNotReachHere();
217
}
218
}
219
return result;
220
}
221
222
223
bool JNIHandles::is_local_handle(Thread* thread, jobject handle) {
224
assert(handle != NULL, "precondition");
225
JNIHandleBlock* block = thread->active_handles();
226
227
// Look back past possible native calls to jni_PushLocalFrame.
228
while (block != NULL) {
229
if (block->chain_contains(handle)) {
230
return true;
231
}
232
block = block->pop_frame_link();
233
}
234
return false;
235
}
236
237
238
// Determine if the handle is somewhere in the current thread's stack.
239
// We easily can't isolate any particular stack frame the handle might
240
// come from, so we'll check the whole stack.
241
242
bool JNIHandles::is_frame_handle(JavaThread* thr, jobject handle) {
243
assert(handle != NULL, "precondition");
244
// If there is no java frame, then this must be top level code, such
245
// as the java command executable, in which case, this type of handle
246
// is not permitted.
247
return (thr->has_last_Java_frame() &&
248
thr->is_in_stack_range_incl((address)handle, (address)thr->last_Java_sp()));
249
}
250
251
252
bool JNIHandles::is_global_handle(jobject handle) {
253
assert(handle != NULL, "precondition");
254
return !is_jweak(handle) && is_storage_handle(global_handles(), jobject_ptr(handle));
255
}
256
257
258
bool JNIHandles::is_weak_global_handle(jobject handle) {
259
assert(handle != NULL, "precondition");
260
return is_jweak(handle) && is_storage_handle(weak_global_handles(), jweak_ptr(handle));
261
}
262
263
size_t JNIHandles::global_handle_memory_usage() {
264
return global_handles()->total_memory_usage();
265
}
266
267
size_t JNIHandles::weak_global_handle_memory_usage() {
268
return weak_global_handles()->total_memory_usage();
269
}
270
271
272
// We assume this is called at a safepoint: no lock is needed.
273
void JNIHandles::print_on(outputStream* st) {
274
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
275
276
st->print_cr("JNI global refs: " SIZE_FORMAT ", weak refs: " SIZE_FORMAT,
277
global_handles()->allocation_count(),
278
weak_global_handles()->allocation_count());
279
st->cr();
280
st->flush();
281
}
282
283
void JNIHandles::print() { print_on(tty); }
284
285
class VerifyJNIHandles: public OopClosure {
286
public:
287
virtual void do_oop(oop* root) {
288
guarantee(oopDesc::is_oop_or_null(RawAccess<>::oop_load(root)), "Invalid oop");
289
}
290
virtual void do_oop(narrowOop* root) { ShouldNotReachHere(); }
291
};
292
293
void JNIHandles::verify() {
294
VerifyJNIHandles verify_handle;
295
296
oops_do(&verify_handle);
297
weak_oops_do(&verify_handle);
298
}
299
300
// This method is implemented here to avoid circular includes between
301
// jniHandles.hpp and thread.hpp.
302
bool JNIHandles::current_thread_in_native() {
303
Thread* thread = Thread::current();
304
return (thread->is_Java_thread() &&
305
thread->as_Java_thread()->thread_state() == _thread_in_native);
306
}
307
308
309
int JNIHandleBlock::_blocks_allocated = 0;
310
JNIHandleBlock* JNIHandleBlock::_block_free_list = NULL;
311
#ifndef PRODUCT
312
JNIHandleBlock* JNIHandleBlock::_block_list = NULL;
313
#endif
314
315
static inline bool is_tagged_free_list(uintptr_t value) {
316
return (value & 1u) != 0;
317
}
318
319
static inline uintptr_t tag_free_list(uintptr_t value) {
320
return value | 1u;
321
}
322
323
static inline uintptr_t untag_free_list(uintptr_t value) {
324
return value & ~(uintptr_t)1u;
325
}
326
327
// There is a freelist of handles running through the JNIHandleBlock
328
// with a tagged next pointer, distinguishing these next pointers from
329
// oops. The freelist handling currently relies on the size of oops
330
// being the same as a native pointer. If this ever changes, then
331
// this freelist handling must change too.
332
STATIC_ASSERT(sizeof(oop) == sizeof(uintptr_t));
333
334
#ifdef ASSERT
335
void JNIHandleBlock::zap() {
336
// Zap block values
337
_top = 0;
338
for (int index = 0; index < block_size_in_oops; index++) {
339
// NOT using Access here; just bare clobbering to NULL, since the
340
// block no longer contains valid oops.
341
_handles[index] = 0;
342
}
343
}
344
#endif // ASSERT
345
346
JNIHandleBlock* JNIHandleBlock::allocate_block(Thread* thread, AllocFailType alloc_failmode) {
347
assert(thread == NULL || thread == Thread::current(), "sanity check");
348
JNIHandleBlock* block;
349
// Check the thread-local free list for a block so we don't
350
// have to acquire a mutex.
351
if (thread != NULL && thread->free_handle_block() != NULL) {
352
block = thread->free_handle_block();
353
thread->set_free_handle_block(block->_next);
354
}
355
else {
356
// locking with safepoint checking introduces a potential deadlock:
357
// - we would hold JNIHandleBlockFreeList_lock and then Threads_lock
358
// - another would hold Threads_lock (jni_AttachCurrentThread) and then
359
// JNIHandleBlockFreeList_lock (JNIHandleBlock::allocate_block)
360
MutexLocker ml(JNIHandleBlockFreeList_lock,
361
Mutex::_no_safepoint_check_flag);
362
if (_block_free_list == NULL) {
363
// Allocate new block
364
if (alloc_failmode == AllocFailStrategy::RETURN_NULL) {
365
block = new (std::nothrow) JNIHandleBlock();
366
if (block == NULL) {
367
return NULL;
368
}
369
} else {
370
block = new JNIHandleBlock();
371
}
372
_blocks_allocated++;
373
block->zap();
374
#ifndef PRODUCT
375
// Link new block to list of all allocated blocks
376
block->_block_list_link = _block_list;
377
_block_list = block;
378
#endif
379
} else {
380
// Get block from free list
381
block = _block_free_list;
382
_block_free_list = _block_free_list->_next;
383
}
384
}
385
block->_top = 0;
386
block->_next = NULL;
387
block->_pop_frame_link = NULL;
388
block->_planned_capacity = block_size_in_oops;
389
// _last, _free_list & _allocate_before_rebuild initialized in allocate_handle
390
debug_only(block->_last = NULL);
391
debug_only(block->_free_list = NULL);
392
debug_only(block->_allocate_before_rebuild = -1);
393
return block;
394
}
395
396
397
void JNIHandleBlock::release_block(JNIHandleBlock* block, Thread* thread) {
398
assert(thread == NULL || thread == Thread::current(), "sanity check");
399
JNIHandleBlock* pop_frame_link = block->pop_frame_link();
400
// Put returned block at the beginning of the thread-local free list.
401
// Note that if thread == NULL, we use it as an implicit argument that
402
// we _don't_ want the block to be kept on the free_handle_block.
403
// See for instance JavaThread::exit().
404
if (thread != NULL ) {
405
block->zap();
406
JNIHandleBlock* freelist = thread->free_handle_block();
407
block->_pop_frame_link = NULL;
408
thread->set_free_handle_block(block);
409
410
// Add original freelist to end of chain
411
if ( freelist != NULL ) {
412
while ( block->_next != NULL ) block = block->_next;
413
block->_next = freelist;
414
}
415
block = NULL;
416
}
417
if (block != NULL) {
418
// Return blocks to free list
419
// locking with safepoint checking introduces a potential deadlock:
420
// - we would hold JNIHandleBlockFreeList_lock and then Threads_lock
421
// - another would hold Threads_lock (jni_AttachCurrentThread) and then
422
// JNIHandleBlockFreeList_lock (JNIHandleBlock::allocate_block)
423
MutexLocker ml(JNIHandleBlockFreeList_lock,
424
Mutex::_no_safepoint_check_flag);
425
while (block != NULL) {
426
block->zap();
427
JNIHandleBlock* next = block->_next;
428
block->_next = _block_free_list;
429
_block_free_list = block;
430
block = next;
431
}
432
}
433
if (pop_frame_link != NULL) {
434
// As a sanity check we release blocks pointed to by the pop_frame_link.
435
// This should never happen (only if PopLocalFrame is not called the
436
// correct number of times).
437
release_block(pop_frame_link, thread);
438
}
439
}
440
441
442
void JNIHandleBlock::oops_do(OopClosure* f) {
443
JNIHandleBlock* current_chain = this;
444
// Iterate over chain of blocks, followed by chains linked through the
445
// pop frame links.
446
while (current_chain != NULL) {
447
for (JNIHandleBlock* current = current_chain; current != NULL;
448
current = current->_next) {
449
assert(current == current_chain || current->pop_frame_link() == NULL,
450
"only blocks first in chain should have pop frame link set");
451
for (int index = 0; index < current->_top; index++) {
452
uintptr_t* addr = &(current->_handles)[index];
453
uintptr_t value = *addr;
454
// traverse heap pointers only, not deleted handles or free list
455
// pointers
456
if (value != 0 && !is_tagged_free_list(value)) {
457
oop* root = (oop*)addr;
458
f->do_oop(root);
459
}
460
}
461
// the next handle block is valid only if current block is full
462
if (current->_top < block_size_in_oops) {
463
break;
464
}
465
}
466
current_chain = current_chain->pop_frame_link();
467
}
468
}
469
470
471
jobject JNIHandleBlock::allocate_handle(oop obj, AllocFailType alloc_failmode) {
472
assert(Universe::heap()->is_in(obj), "sanity check");
473
if (_top == 0) {
474
// This is the first allocation or the initial block got zapped when
475
// entering a native function. If we have any following blocks they are
476
// not valid anymore.
477
for (JNIHandleBlock* current = _next; current != NULL;
478
current = current->_next) {
479
assert(current->_last == NULL, "only first block should have _last set");
480
assert(current->_free_list == NULL,
481
"only first block should have _free_list set");
482
if (current->_top == 0) {
483
// All blocks after the first clear trailing block are already cleared.
484
#ifdef ASSERT
485
for (current = current->_next; current != NULL; current = current->_next) {
486
assert(current->_top == 0, "trailing blocks must already be cleared");
487
}
488
#endif
489
break;
490
}
491
current->_top = 0;
492
current->zap();
493
}
494
// Clear initial block
495
_free_list = NULL;
496
_allocate_before_rebuild = 0;
497
_last = this;
498
zap();
499
}
500
501
// Try last block
502
if (_last->_top < block_size_in_oops) {
503
oop* handle = (oop*)&(_last->_handles)[_last->_top++];
504
NativeAccess<IS_DEST_UNINITIALIZED>::oop_store(handle, obj);
505
return (jobject) handle;
506
}
507
508
// Try free list
509
if (_free_list != NULL) {
510
oop* handle = (oop*)_free_list;
511
_free_list = (uintptr_t*) untag_free_list(*_free_list);
512
NativeAccess<IS_DEST_UNINITIALIZED>::oop_store(handle, obj);
513
return (jobject) handle;
514
}
515
// Check if unused block follow last
516
if (_last->_next != NULL) {
517
// update last and retry
518
_last = _last->_next;
519
return allocate_handle(obj, alloc_failmode);
520
}
521
522
// No space available, we have to rebuild free list or expand
523
if (_allocate_before_rebuild == 0) {
524
rebuild_free_list(); // updates _allocate_before_rebuild counter
525
} else {
526
// Append new block
527
Thread* thread = Thread::current();
528
Handle obj_handle(thread, obj);
529
// This can block, so we need to preserve obj across call.
530
_last->_next = JNIHandleBlock::allocate_block(thread, alloc_failmode);
531
if (_last->_next == NULL) {
532
return NULL;
533
}
534
_last = _last->_next;
535
_allocate_before_rebuild--;
536
obj = obj_handle();
537
}
538
return allocate_handle(obj, alloc_failmode); // retry
539
}
540
541
void JNIHandleBlock::rebuild_free_list() {
542
assert(_allocate_before_rebuild == 0 && _free_list == NULL, "just checking");
543
int free = 0;
544
int blocks = 0;
545
for (JNIHandleBlock* current = this; current != NULL; current = current->_next) {
546
for (int index = 0; index < current->_top; index++) {
547
uintptr_t* handle = &(current->_handles)[index];
548
if (*handle == 0) {
549
// this handle was cleared out by a delete call, reuse it
550
*handle = _free_list == NULL ? 0 : tag_free_list((uintptr_t)_free_list);
551
_free_list = handle;
552
free++;
553
}
554
}
555
// we should not rebuild free list if there are unused handles at the end
556
assert(current->_top == block_size_in_oops, "just checking");
557
blocks++;
558
}
559
// Heuristic: if more than half of the handles are free we rebuild next time
560
// as well, otherwise we append a corresponding number of new blocks before
561
// attempting a free list rebuild again.
562
int total = blocks * block_size_in_oops;
563
int extra = total - 2*free;
564
if (extra > 0) {
565
// Not as many free handles as we would like - compute number of new blocks to append
566
_allocate_before_rebuild = (extra + block_size_in_oops - 1) / block_size_in_oops;
567
}
568
}
569
570
571
bool JNIHandleBlock::contains(jobject handle) const {
572
return ((jobject)&_handles[0] <= handle && handle<(jobject)&_handles[_top]);
573
}
574
575
576
bool JNIHandleBlock::chain_contains(jobject handle) const {
577
for (JNIHandleBlock* current = (JNIHandleBlock*) this; current != NULL; current = current->_next) {
578
if (current->contains(handle)) {
579
return true;
580
}
581
}
582
return false;
583
}
584
585
586
size_t JNIHandleBlock::length() const {
587
size_t result = 1;
588
for (JNIHandleBlock* current = _next; current != NULL; current = current->_next) {
589
result++;
590
}
591
return result;
592
}
593
594
class CountJNIHandleClosure: public OopClosure {
595
private:
596
int _count;
597
public:
598
CountJNIHandleClosure(): _count(0) {}
599
virtual void do_oop(oop* ooph) { _count++; }
600
virtual void do_oop(narrowOop* unused) { ShouldNotReachHere(); }
601
int count() { return _count; }
602
};
603
604
const size_t JNIHandleBlock::get_number_of_live_handles() {
605
CountJNIHandleClosure counter;
606
oops_do(&counter);
607
return counter.count();
608
}
609
610
// This method is not thread-safe, i.e., must be called while holding a lock on the
611
// structure.
612
size_t JNIHandleBlock::memory_usage() const {
613
return length() * sizeof(JNIHandleBlock);
614
}
615
616
617
#ifndef PRODUCT
618
619
bool JNIHandles::is_local_handle(jobject handle) {
620
return JNIHandleBlock::any_contains(handle);
621
}
622
623
bool JNIHandleBlock::any_contains(jobject handle) {
624
assert(handle != NULL, "precondition");
625
for (JNIHandleBlock* current = _block_list; current != NULL; current = current->_block_list_link) {
626
if (current->contains(handle)) {
627
return true;
628
}
629
}
630
return false;
631
}
632
633
void JNIHandleBlock::print_statistics() {
634
int used_blocks = 0;
635
int free_blocks = 0;
636
int used_handles = 0;
637
int free_handles = 0;
638
JNIHandleBlock* block = _block_list;
639
while (block != NULL) {
640
if (block->_top > 0) {
641
used_blocks++;
642
} else {
643
free_blocks++;
644
}
645
used_handles += block->_top;
646
free_handles += (block_size_in_oops - block->_top);
647
block = block->_block_list_link;
648
}
649
tty->print_cr("JNIHandleBlocks statistics");
650
tty->print_cr("- blocks allocated: %d", used_blocks + free_blocks);
651
tty->print_cr("- blocks in use: %d", used_blocks);
652
tty->print_cr("- blocks free: %d", free_blocks);
653
tty->print_cr("- handles in use: %d", used_handles);
654
tty->print_cr("- handles free: %d", free_handles);
655
}
656
657
#endif
658
659