View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.internal.storage.file;
13  
14  import java.io.IOException;
15  import java.lang.ref.ReferenceQueue;
16  import java.lang.ref.SoftReference;
17  import java.util.Collections;
18  import java.util.Map;
19  import java.util.Random;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.concurrent.ConcurrentLinkedQueue;
22  import java.util.concurrent.atomic.AtomicLong;
23  import java.util.concurrent.atomic.AtomicReferenceArray;
24  import java.util.concurrent.atomic.LongAdder;
25  import java.util.concurrent.locks.ReentrantLock;
26  import java.util.stream.Collectors;
27  
28  import org.eclipse.jgit.annotations.NonNull;
29  import org.eclipse.jgit.internal.JGitText;
30  import org.eclipse.jgit.storage.file.WindowCacheConfig;
31  import org.eclipse.jgit.storage.file.WindowCacheStats;
32  import org.eclipse.jgit.util.Monitoring;
33  
34  /**
35   * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in
36   * memory for faster read access.
37   * <p>
38   * The WindowCache serves as a Java based "buffer cache", loading segments of a
39   * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
40   * only tiny slices of a file, the WindowCache tries to smooth out these tiny
41   * reads into larger block-sized IO operations.
42   * <p>
43   * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by
44   * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
45   * This is ensured by an array of locks, with the tuple hashed to a lock
46   * instance.
47   * <p>
48   * During a miss, older entries are evicted from the cache so long as
49   * {@link #isFull()} returns true.
50   * <p>
51   * Its too expensive during object access to be 100% accurate with a least
52   * recently used (LRU) algorithm. Strictly ordering every read is a lot of
53   * overhead that typically doesn't yield a corresponding benefit to the
54   * application.
55   * <p>
56   * This cache implements a loose LRU policy by randomly picking a window
57   * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
58   * within that window.
59   * <p>
60   * Entities created by the cache are held under SoftReferences if option
61   * {@code core.packedGitUseStrongRefs} is set to {@code false} in the git config
62   * (this is the default) or by calling
63   * {@link WindowCacheConfig#setPackedGitUseStrongRefs(boolean)}, permitting the
64   * Java runtime's garbage collector to evict entries when heap memory gets low.
65   * Most JREs implement a loose least recently used algorithm for this eviction.
66   * When this option is set to {@code true} strong references are used which
67   * means that Java gc cannot evict the WindowCache to reclaim memory. On the
68   * other hand this provides more predictable performance since the cache isn't
69   * flushed when used heap comes close to the maximum heap size.
70   * <p>
71   * The internal hash table does not expand at runtime, instead it is fixed in
72   * size at cache creation time. The internal lock table used to gate load
73   * invocations is also fixed in size.
74   * <p>
75   * The key tuple is passed through to methods as a pair of parameters rather
76   * than as a single Object, thus reducing the transient memory allocations of
77   * callers. It is more efficient to avoid the allocation, as we can't be 100%
78   * sure that a JIT would be able to stack-allocate a key tuple.
79   * <p>
80   * This cache has an implementation rule such that:
81   * <ul>
82   * <li>{@link #load(PackFile, long)} is invoked by at most one thread at a time
83   * for a given <code>(PackFile,position)</code> tuple.</li>
84   * <li>For every <code>load()</code> invocation there is exactly one
85   * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a
86   * SoftReference or a StrongReference around the cached entity.</li>
87   * <li>For every Reference created by <code>createRef()</code> there will be
88   * exactly one call to {@link #clear(PageRef)} to cleanup any resources associated
89   * with the (now expired) cached entity.</li>
90   * </ul>
91   * <p>
92   * Therefore, it is safe to perform resource accounting increments during the
93   * {@link #load(PackFile, long)} or
94   * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching
95   * decrements during {@link #clear(PageRef)}. Implementors may need to override
96   * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional
97   * accounting information into an implementation specific
98   * {@link org.eclipse.jgit.internal.storage.file.WindowCache.PageRef} subclass, as
99   * the cached entity may have already been evicted by the JRE's garbage
100  * collector.
101  * <p>
102  * To maintain higher concurrency workloads, during eviction only one thread
103  * performs the eviction work, while other threads can continue to insert new
104  * objects in parallel. This means that the cache can be temporarily over limit,
105  * especially if the nominated eviction thread is being starved relative to the
106  * other threads.
107  */
108 public class WindowCache {
109 
110 	/**
111 	 * Record statistics for a cache
112 	 */
113 	static interface StatsRecorder {
114 		/**
115 		 * Record cache hits. Called when cache returns a cached entry.
116 		 *
117 		 * @param count
118 		 *            number of cache hits to record
119 		 */
120 		void recordHits(int count);
121 
122 		/**
123 		 * Record cache misses. Called when the cache returns an entry which had
124 		 * to be loaded.
125 		 *
126 		 * @param count
127 		 *            number of cache misses to record
128 		 */
129 		void recordMisses(int count);
130 
131 		/**
132 		 * Record a successful load of a cache entry
133 		 *
134 		 * @param loadTimeNanos
135 		 *            time to load a cache entry
136 		 */
137 		void recordLoadSuccess(long loadTimeNanos);
138 
139 		/**
140 		 * Record a failed load of a cache entry
141 		 *
142 		 * @param loadTimeNanos
143 		 *            time used trying to load a cache entry
144 		 */
145 		void recordLoadFailure(long loadTimeNanos);
146 
147 		/**
148 		 * Record cache evictions due to the cache evictions strategy
149 		 *
150 		 * @param count
151 		 *            number of evictions to record
152 		 */
153 		void recordEvictions(int count);
154 
155 		/**
156 		 * Record files opened by cache
157 		 *
158 		 * @param delta
159 		 *            delta of number of files opened by cache
160 		 */
161 		void recordOpenFiles(int delta);
162 
163 		/**
164 		 * Record cached bytes
165 		 *
166 		 * @param pack
167 		 *            pack file the bytes are read from
168 		 *
169 		 * @param delta
170 		 *            delta of cached bytes
171 		 */
172 		void recordOpenBytes(PackFile pack, int delta);
173 
174 		/**
175 		 * Returns a snapshot of this recorder's stats. Note that this may be an
176 		 * inconsistent view, as it may be interleaved with update operations.
177 		 *
178 		 * @return a snapshot of this recorder's stats
179 		 */
180 		@NonNull
181 		WindowCacheStats getStats();
182 	}
183 
184 	static class StatsRecorderImpl
185 			implements StatsRecorder, WindowCacheStats {
186 		private final LongAdder hitCount;
187 		private final LongAdder missCount;
188 		private final LongAdder loadSuccessCount;
189 		private final LongAdder loadFailureCount;
190 		private final LongAdder totalLoadTime;
191 		private final LongAdder evictionCount;
192 		private final LongAdder openFileCount;
193 		private final LongAdder openByteCount;
194 		private final Map<String, LongAdder> openByteCountPerRepository;
195 
196 		/**
197 		 * Constructs an instance with all counts initialized to zero.
198 		 */
199 		public StatsRecorderImpl() {
200 			hitCount = new LongAdder();
201 			missCount = new LongAdder();
202 			loadSuccessCount = new LongAdder();
203 			loadFailureCount = new LongAdder();
204 			totalLoadTime = new LongAdder();
205 			evictionCount = new LongAdder();
206 			openFileCount = new LongAdder();
207 			openByteCount = new LongAdder();
208 			openByteCountPerRepository = new ConcurrentHashMap<>();
209 		}
210 
211 		@Override
212 		public void recordHits(int count) {
213 			hitCount.add(count);
214 		}
215 
216 		@Override
217 		public void recordMisses(int count) {
218 			missCount.add(count);
219 		}
220 
221 		@Override
222 		public void recordLoadSuccess(long loadTimeNanos) {
223 			loadSuccessCount.increment();
224 			totalLoadTime.add(loadTimeNanos);
225 		}
226 
227 		@Override
228 		public void recordLoadFailure(long loadTimeNanos) {
229 			loadFailureCount.increment();
230 			totalLoadTime.add(loadTimeNanos);
231 		}
232 
233 		@Override
234 		public void recordEvictions(int count) {
235 			evictionCount.add(count);
236 		}
237 
238 		@Override
239 		public void recordOpenFiles(int delta) {
240 			openFileCount.add(delta);
241 		}
242 
243 		@Override
244 		public void recordOpenBytes(PackFile pack, int delta) {
245 			openByteCount.add(delta);
246 			String repositoryId = repositoryId(pack);
247 			LongAdder la = openByteCountPerRepository
248 					.computeIfAbsent(repositoryId, k -> new LongAdder());
249 			la.add(delta);
250 			if (delta < 0) {
251 				openByteCountPerRepository.computeIfPresent(repositoryId,
252 						(k, v) -> v.longValue() == 0 ? null : v);
253 			}
254 		}
255 
256 		private static String repositoryId(PackFile pack) {
257 			// use repository's gitdir since packfile doesn't know its
258 			// repository
259 			return pack.getPackFile().getParentFile().getParentFile()
260 					.getParent();
261 		}
262 
263 		@Override
264 		public WindowCacheStats getStats() {
265 			return this;
266 		}
267 
268 		@Override
269 		public long getHitCount() {
270 			return hitCount.sum();
271 		}
272 
273 		@Override
274 		public long getMissCount() {
275 			return missCount.sum();
276 		}
277 
278 		@Override
279 		public long getLoadSuccessCount() {
280 			return loadSuccessCount.sum();
281 		}
282 
283 		@Override
284 		public long getLoadFailureCount() {
285 			return loadFailureCount.sum();
286 		}
287 
288 		@Override
289 		public long getEvictionCount() {
290 			return evictionCount.sum();
291 		}
292 
293 		@Override
294 		public long getTotalLoadTime() {
295 			return totalLoadTime.sum();
296 		}
297 
298 		@Override
299 		public long getOpenFileCount() {
300 			return openFileCount.sum();
301 		}
302 
303 		@Override
304 		public long getOpenByteCount() {
305 			return openByteCount.sum();
306 		}
307 
308 		@Override
309 		public void resetCounters() {
310 			hitCount.reset();
311 			missCount.reset();
312 			loadSuccessCount.reset();
313 			loadFailureCount.reset();
314 			totalLoadTime.reset();
315 			evictionCount.reset();
316 		}
317 
318 		@Override
319 		public Map<String, Long> getOpenByteCountPerRepository() {
320 			return Collections.unmodifiableMap(
321 					openByteCountPerRepository.entrySet().stream()
322 							.collect(Collectors.toMap(Map.Entry::getKey,
323 									e -> Long.valueOf(e.getValue().sum()),
324 									(u, v) -> v)));
325 		}
326 	}
327 
328 	private static final int bits(int newSize) {
329 		if (newSize < 4096)
330 			throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
331 		if (Integer.bitCount(newSize) != 1)
332 			throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
333 		return Integer.numberOfTrailingZeros(newSize);
334 	}
335 
336 	private static final Random rng = new Random();
337 
338 	private static volatile WindowCache cache;
339 
340 	private static volatile int streamFileThreshold;
341 
342 	static {
343 		reconfigure(new WindowCacheConfig());
344 	}
345 
346 	/**
347 	 * Modify the configuration of the window cache.
348 	 * <p>
349 	 * The new configuration is applied immediately. If the new limits are
350 	 * smaller than what is currently cached, older entries will be purged
351 	 * as soon as possible to allow the cache to meet the new limit.
352 	 *
353 	 * @deprecated use {@code cfg.install()} to avoid internal reference.
354 	 * @param cfg
355 	 *            the new window cache configuration.
356 	 * @throws java.lang.IllegalArgumentException
357 	 *             the cache configuration contains one or more invalid
358 	 *             settings, usually too low of a limit.
359 	 */
360 	@Deprecated
361 	public static void reconfigure(WindowCacheConfig cfg) {
362 		final WindowCacheal/storage/file/WindowCache.html#WindowCache">WindowCache nc = new WindowCache(cfg);
363 		final WindowCache oc = cache;
364 		if (oc != null)
365 			oc.removeAll();
366 		cache = nc;
367 		streamFileThreshold = cfg.getStreamFileThreshold();
368 		DeltaBaseCache.reconfigure(cfg);
369 	}
370 
371 	static int getStreamFileThreshold() {
372 		return streamFileThreshold;
373 	}
374 
375 	/**
376 	 * @return the cached instance.
377 	 */
378 	public static WindowCache getInstance() {
379 		return cache;
380 	}
381 
382 	static final ByteWindow get(PackFile pack, long offset)
383 			throws IOException {
384 		final WindowCache c = cache;
385 		final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
386 		if (c != cache) {
387 			// The cache was reconfigured while we were using the old one
388 			// to load this window. The window is still valid, but our
389 			// cache may think its still live. Ensure the window is removed
390 			// from the old cache so resources can be released.
391 			//
392 			c.removeAll();
393 		}
394 		return r;
395 	}
396 
397 	static final void purge(PackFile pack) {
398 		cache.removeAll(pack);
399 	}
400 
401 	/** cleanup released and/or garbage collected windows. */
402 	private final CleanupQueue queue;
403 
404 	/** Number of entries in {@link #table}. */
405 	private final int tableSize;
406 
407 	/** Access clock for loose LRU. */
408 	private final AtomicLong clock;
409 
410 	/** Hash bucket directory; entries are chained below. */
411 	private final AtomicReferenceArray<Entry> table;
412 
413 	/** Locks to prevent concurrent loads for same (PackFile,position). */
414 	private final Lock[] locks;
415 
416 	/** Lock to elect the eviction thread after a load occurs. */
417 	private final ReentrantLock evictLock;
418 
419 	/** Number of {@link #table} buckets to scan for an eviction window. */
420 	private final int evictBatch;
421 
422 	private final int maxFiles;
423 
424 	private final long maxBytes;
425 
426 	private final boolean mmap;
427 
428 	private final int windowSizeShift;
429 
430 	private final int windowSize;
431 
432 	private final StatsRecorder statsRecorder;
433 
434 	private final StatsRecorderImpl mbean;
435 
436 	private boolean useStrongRefs;
437 
438 	private WindowCache(WindowCacheConfig cfg) {
439 		tableSize = tableSize(cfg);
440 		final int lockCount = lockCount(cfg);
441 		if (tableSize < 1)
442 			throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
443 		if (lockCount < 1)
444 			throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
445 
446 		clock = new AtomicLong(1);
447 		table = new AtomicReferenceArray<>(tableSize);
448 		locks = new Lock[lockCount];
449 		for (int i = 0; i < locks.length; i++)
450 			locks[i] = new Lock();
451 		evictLock = new ReentrantLock();
452 
453 		int eb = (int) (tableSize * .1);
454 		if (64 < eb)
455 			eb = 64;
456 		else if (eb < 4)
457 			eb = 4;
458 		if (tableSize < eb)
459 			eb = tableSize;
460 		evictBatch = eb;
461 
462 		maxFiles = cfg.getPackedGitOpenFiles();
463 		maxBytes = cfg.getPackedGitLimit();
464 		mmap = cfg.isPackedGitMMAP();
465 		windowSizeShift = bits(cfg.getPackedGitWindowSize());
466 		windowSize = 1 << windowSizeShift;
467 		useStrongRefs = cfg.isPackedGitUseStrongRefs();
468 		queue = useStrongRefs ? new StrongCleanupQueue(this)
469 				: new SoftCleanupQueue(this);
470 
471 		mbean = new StatsRecorderImpl();
472 		statsRecorder = mbean;
473 		Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
474 
475 		if (maxFiles < 1)
476 			throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
477 		if (maxBytes < windowSize)
478 			throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
479 	}
480 
481 	/**
482 	 * @return cache statistics for the WindowCache
483 	 */
484 	public WindowCacheStats getStats() {
485 		return statsRecorder.getStats();
486 	}
487 
488 	/**
489 	 * Reset stats. Does not reset open bytes and open files stats.
490 	 */
491 	public void resetStats() {
492 		mbean.resetCounters();
493 	}
494 
495 	private int hash(int packHash, long off) {
496 		return packHash + (int) (off >>> windowSizeShift);
497 	}
498 
499 	private ByteWindow load(PackFile pack, long offset) throws IOException {
500 		long startTime = System.nanoTime();
501 		if (pack.beginWindowCache())
502 			statsRecorder.recordOpenFiles(1);
503 		try {
504 			if (mmap)
505 				return pack.mmap(offset, windowSize);
506 			ByteArrayWindow w = pack.read(offset, windowSize);
507 			statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
508 			return w;
509 		} catch (IOException | RuntimeException | Error e) {
510 			close(pack);
511 			statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
512 			throw e;
513 		} finally {
514 			statsRecorder.recordMisses(1);
515 		}
516 	}
517 
518 	private PageRef<ByteWindow> createRef(PackFile p, long o, ByteWindow v) {
519 		final PageRef<ByteWindow> ref = useStrongRefs
520 				? new StrongRef(p, o, v, queue)
521 				: new SoftRef(p, o, v, (SoftCleanupQueue) queue);
522 		statsRecorder.recordOpenBytes(ref.getPack(), ref.getSize());
523 		return ref;
524 	}
525 
526 	private void clear(PageRef<ByteWindow> ref) {
527 		statsRecorder.recordOpenBytes(ref.getPack(), -ref.getSize());
528 		statsRecorder.recordEvictions(1);
529 		close(ref.getPack());
530 	}
531 
532 	private void close(PackFile pack) {
533 		if (pack.endWindowCache()) {
534 			statsRecorder.recordOpenFiles(-1);
535 		}
536 	}
537 
538 	private boolean isFull() {
539 		return maxFiles < mbean.getOpenFileCount()
540 				|| maxBytes < mbean.getOpenByteCount();
541 	}
542 
543 	private long toStart(long offset) {
544 		return (offset >>> windowSizeShift) << windowSizeShift;
545 	}
546 
547 	private static int tableSize(WindowCacheConfig cfg) {
548 		final int wsz = cfg.getPackedGitWindowSize();
549 		final long limit = cfg.getPackedGitLimit();
550 		if (wsz <= 0)
551 			throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
552 		if (limit < wsz)
553 			throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
554 		return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
555 	}
556 
557 	private static int lockCount(WindowCacheConfig cfg) {
558 		return Math.max(cfg.getPackedGitOpenFiles(), 32);
559 	}
560 
561 	/**
562 	 * Lookup a cached object, creating and loading it if it doesn't exist.
563 	 *
564 	 * @param pack
565 	 *            the pack that "contains" the cached object.
566 	 * @param position
567 	 *            offset within <code>pack</code> of the object.
568 	 * @return the object reference.
569 	 * @throws IOException
570 	 *             the object reference was not in the cache and could not be
571 	 *             obtained by {@link #load(PackFile, long)}.
572 	 */
573 	private ByteWindow getOrLoad(PackFile pack, long position)
574 			throws IOException {
575 		final int slot = slot(pack, position);
576 		final Entry e1 = table.get(slot);
577 		ByteWindow v = scan(e1, pack, position);
578 		if (v != null) {
579 			statsRecorder.recordHits(1);
580 			return v;
581 		}
582 
583 		synchronized (lock(pack, position)) {
584 			Entry e2 = table.get(slot);
585 			if (e2 != e1) {
586 				v = scan(e2, pack, position);
587 				if (v != null) {
588 					statsRecorder.recordHits(1);
589 					return v;
590 				}
591 			}
592 
593 			v = load(pack, position);
594 			final PageRef<ByteWindow> ref = createRef(pack, position, v);
595 			hit(ref);
596 			for (;;) {
597 				final Entry n = new Entry(clean(e2), ref);
598 				if (table.compareAndSet(slot, e2, n))
599 					break;
600 				e2 = table.get(slot);
601 			}
602 		}
603 
604 		if (evictLock.tryLock()) {
605 			try {
606 				gc();
607 				evict();
608 			} finally {
609 				evictLock.unlock();
610 			}
611 		}
612 
613 		return v;
614 	}
615 
616 	private ByteWindow scan(Entry n, PackFile pack, long position) {
617 		for (; n != null; n = n.next) {
618 			final PageRef<ByteWindow> r = n.ref;
619 			if (r.getPack() == pack && r.getPosition() == position) {
620 				final ByteWindow v = r.get();
621 				if (v != null) {
622 					hit(r);
623 					return v;
624 				}
625 				n.kill();
626 				break;
627 			}
628 		}
629 		return null;
630 	}
631 
632 	private void hit(PageRef r) {
633 		// We don't need to be 100% accurate here. Its sufficient that at least
634 		// one thread performs the increment. Any other concurrent access at
635 		// exactly the same time can simply use the same clock value.
636 		//
637 		// Consequently we attempt the set, but we don't try to recover should
638 		// it fail. This is why we don't use getAndIncrement() here.
639 		//
640 		final long c = clock.get();
641 		clock.compareAndSet(c, c + 1);
642 		r.setLastAccess(c);
643 	}
644 
645 	private void evict() {
646 		while (isFull()) {
647 			int ptr = rng.nextInt(tableSize);
648 			Entry old = null;
649 			int slot = 0;
650 			for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
651 				if (tableSize <= ptr)
652 					ptr = 0;
653 				for (Entry e = table.get(ptr); e != null; e = e.next) {
654 					if (e.dead)
655 						continue;
656 					if (old == null || e.ref.getLastAccess() < old.ref
657 							.getLastAccess()) {
658 						old = e;
659 						slot = ptr;
660 					}
661 				}
662 			}
663 			if (old != null) {
664 				old.kill();
665 				gc();
666 				final Entry e1 = table.get(slot);
667 				table.compareAndSet(slot, e1, clean(e1));
668 			}
669 		}
670 	}
671 
672 	/**
673 	 * Clear every entry from the cache.
674 	 * <p>
675 	 * This is a last-ditch effort to clear out the cache, such as before it
676 	 * gets replaced by another cache that is configured differently. This
677 	 * method tries to force every cached entry through {@link #clear(PageRef)} to
678 	 * ensure that resources are correctly accounted for and cleaned up by the
679 	 * subclass. A concurrent reader loading entries while this method is
680 	 * running may cause resource accounting failures.
681 	 */
682 	private void removeAll() {
683 		for (int s = 0; s < tableSize; s++) {
684 			Entry e1;
685 			do {
686 				e1 = table.get(s);
687 				for (Entry e = e1; e != null; e = e.next)
688 					e.kill();
689 			} while (!table.compareAndSet(s, e1, null));
690 		}
691 		gc();
692 	}
693 
694 	/**
695 	 * Clear all entries related to a single file.
696 	 * <p>
697 	 * Typically this method is invoked during {@link PackFile#close()}, when we
698 	 * know the pack is never going to be useful to us again (for example, it no
699 	 * longer exists on disk). A concurrent reader loading an entry from this
700 	 * same pack may cause the pack to become stuck in the cache anyway.
701 	 *
702 	 * @param pack
703 	 *            the file to purge all entries of.
704 	 */
705 	private void removeAll(PackFile pack) {
706 		for (int s = 0; s < tableSize; s++) {
707 			final Entry e1 = table.get(s);
708 			boolean hasDead = false;
709 			for (Entry e = e1; e != null; e = e.next) {
710 				if (e.ref.getPack() == pack) {
711 					e.kill();
712 					hasDead = true;
713 				} else if (e.dead)
714 					hasDead = true;
715 			}
716 			if (hasDead)
717 				table.compareAndSet(s, e1, clean(e1));
718 		}
719 		gc();
720 	}
721 
722 	private void gc() {
723 		queue.gc();
724 	}
725 
726 	private int slot(PackFile pack, long position) {
727 		return (hash(pack.hash, position) >>> 1) % tableSize;
728 	}
729 
730 	private Lock lock(PackFile pack, long position) {
731 		return locks[(hash(pack.hash, position) >>> 1) % locks.length];
732 	}
733 
734 	private static Entry clean(Entry top) {
735 		while (top != null && top.dead) {
736 			top.ref.kill();
737 			top = top.next;
738 		}
739 		if (top == null)
740 			return null;
741 		final Entry n = clean(top.next);
742 		return n == top.next ? top : new Entry(n, top.ref);
743 	}
744 
745 	private static class Entry {
746 		/** Next entry in the hash table's chain list. */
747 		final Entry next;
748 
749 		/** The referenced object. */
750 		final PageRef<ByteWindow> ref;
751 
752 		/**
753 		 * Marked true when ref.get() returns null and the ref is dead.
754 		 * <p>
755 		 * A true here indicates that the ref is no longer accessible, and that
756 		 * we therefore need to eventually purge this Entry object out of the
757 		 * bucket's chain.
758 		 */
759 		volatile boolean dead;
760 
761 		Entry(Entry n, PageRef<ByteWindow> r) {
762 			next = n;
763 			ref = r;
764 		}
765 
766 		final void kill() {
767 			dead = true;
768 			ref.kill();
769 		}
770 	}
771 
772 	private static interface PageRef<T> {
773 		/**
774 		 * Returns this reference object's referent. If this reference object
775 		 * has been cleared, either by the program or by the garbage collector,
776 		 * then this method returns <code>null</code>.
777 		 *
778 		 * @return The object to which this reference refers, or
779 		 *         <code>null</code> if this reference object has been cleared
780 		 */
781 		T get();
782 
783 	    /**
784 		 * Kill this ref
785 		 *
786 		 * @return <code>true</code> if this reference object was successfully
787 		 *         killed; <code>false</code> if it was already killed
788 		 */
789 		boolean kill();
790 
791 		/**
792 		 * Get the packfile the referenced cache page is allocated for
793 		 *
794 		 * @return the packfile the referenced cache page is allocated for
795 		 */
796 		PackFile getPack();
797 
798 		/**
799 		 * Get the position of the referenced cache page in the packfile
800 		 *
801 		 * @return the position of the referenced cache page in the packfile
802 		 */
803 		long getPosition();
804 
805 		/**
806 		 * Get size of cache page
807 		 *
808 		 * @return size of cache page
809 		 */
810 		int getSize();
811 
812 		/**
813 		 * Get pseudo time of last access to this cache page
814 		 *
815 		 * @return pseudo time of last access to this cache page
816 		 */
817 		long getLastAccess();
818 
819 		/**
820 		 * Set pseudo time of last access to this cache page
821 		 *
822 		 * @param time
823 		 *            pseudo time of last access to this cache page
824 		 */
825 		void setLastAccess(long time);
826 
827 		/**
828 		 * Whether this is a strong reference.
829 		 * @return {@code true} if this is a strong reference
830 		 */
831 		boolean isStrongRef();
832 	}
833 
834 	/** A soft reference wrapped around a cached object. */
835 	private static class SoftRef extends SoftReference<ByteWindow>
836 			implements PageRef<ByteWindow> {
837 		private final PackFile pack;
838 
839 		private final long position;
840 
841 		private final int size;
842 
843 		private long lastAccess;
844 
845 		protected SoftRef(final PackFile pack, final long position,
846 				final ByteWindow v, final SoftCleanupQueue queue) {
847 			super(v, queue);
848 			this.pack = pack;
849 			this.position = position;
850 			this.size = v.size();
851 		}
852 
853 		@Override
854 		public PackFile getPack() {
855 			return pack;
856 		}
857 
858 		@Override
859 		public long getPosition() {
860 			return position;
861 		}
862 
863 		@Override
864 		public int getSize() {
865 			return size;
866 		}
867 
868 		@Override
869 		public long getLastAccess() {
870 			return lastAccess;
871 		}
872 
873 		@Override
874 		public void setLastAccess(long time) {
875 			this.lastAccess = time;
876 		}
877 
878 		@Override
879 		public boolean kill() {
880 			return enqueue();
881 		}
882 
883 		@Override
884 		public boolean isStrongRef() {
885 			return false;
886 		}
887 	}
888 
889 	/** A strong reference wrapped around a cached object. */
890 	private static class StrongRef implements PageRef<ByteWindow> {
891 		private ByteWindow referent;
892 
893 		private final PackFile pack;
894 
895 		private final long position;
896 
897 		private final int size;
898 
899 		private long lastAccess;
900 
901 		private CleanupQueue queue;
902 
903 		protected StrongRef(final PackFile pack, final long position,
904 				final ByteWindow v, final CleanupQueue queue) {
905 			this.pack = pack;
906 			this.position = position;
907 			this.referent = v;
908 			this.size = v.size();
909 			this.queue = queue;
910 		}
911 
912 		@Override
913 		public PackFile getPack() {
914 			return pack;
915 		}
916 
917 		@Override
918 		public long getPosition() {
919 			return position;
920 		}
921 
922 		@Override
923 		public int getSize() {
924 			return size;
925 		}
926 
927 		@Override
928 		public long getLastAccess() {
929 			return lastAccess;
930 		}
931 
932 		@Override
933 		public void setLastAccess(long time) {
934 			this.lastAccess = time;
935 		}
936 
937 		@Override
938 		public ByteWindow get() {
939 			return referent;
940 		}
941 
942 		@Override
943 		public boolean kill() {
944 			if (referent == null) {
945 				return false;
946 			}
947 			referent = null;
948 			return queue.enqueue(this);
949 		}
950 
951 		@Override
952 		public boolean isStrongRef() {
953 			return true;
954 		}
955 	}
956 
957 	private static interface CleanupQueue {
958 		boolean enqueue(PageRef<ByteWindow> r);
959 		void gc();
960 	}
961 
962 	private static class SoftCleanupQueue extends ReferenceQueue<ByteWindow>
963 			implements CleanupQueue {
964 		private final WindowCache wc;
965 
966 		SoftCleanupQueue(WindowCache cache) {
967 			this.wc = cache;
968 		}
969 
970 		@Override
971 		public boolean enqueue(PageRef<ByteWindow> r) {
972 			// no need to explicitly add soft references which are enqueued by
973 			// the JVM
974 			return false;
975 		}
976 
977 		@Override
978 		public void gc() {
979 			SoftRef r;
980 			while ((r = (SoftRef) poll()) != null) {
981 				wc.clear(r);
982 
983 				final int s = wc.slot(r.getPack(), r.getPosition());
984 				final Entry e1 = wc.table.get(s);
985 				for (Entry n = e1; n != null; n = n.next) {
986 					if (n.ref == r) {
987 						n.dead = true;
988 						wc.table.compareAndSet(s, e1, clean(e1));
989 						break;
990 					}
991 				}
992 			}
993 		}
994 	}
995 
996 	private static class StrongCleanupQueue implements CleanupQueue {
997 		private final WindowCache wc;
998 
999 		private final ConcurrentLinkedQueue<PageRef<ByteWindow>> queue = new ConcurrentLinkedQueue<>();
1000 
1001 		StrongCleanupQueue(WindowCache wc) {
1002 			this.wc = wc;
1003 		}
1004 
1005 		@Override
1006 		public boolean enqueue(PageRef<ByteWindow> r) {
1007 			if (queue.contains(r)) {
1008 				return false;
1009 			}
1010 			return queue.add(r);
1011 		}
1012 
1013 		@Override
1014 		public void gc() {
1015 			PageRef<ByteWindow> r;
1016 			while ((r = queue.poll()) != null) {
1017 				wc.clear(r);
1018 
1019 				final int s = wc.slot(r.getPack(), r.getPosition());
1020 				final Entry e1 = wc.table.get(s);
1021 				for (Entry n = e1; n != null; n = n.next) {
1022 					if (n.ref == r) {
1023 						n.dead = true;
1024 						wc.table.compareAndSet(s, e1, clean(e1));
1025 						break;
1026 					}
1027 				}
1028 			}
1029 		}
1030 	}
1031 
1032 	private static final class Lock {
1033 		// Used only for its implicit monitor.
1034 	}
1035 }