You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WindowCache.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  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. package org.eclipse.jgit.internal.storage.file;
  12. import java.io.IOException;
  13. import java.lang.ref.ReferenceQueue;
  14. import java.lang.ref.SoftReference;
  15. import java.util.Random;
  16. import java.util.concurrent.atomic.AtomicLong;
  17. import java.util.concurrent.atomic.AtomicReferenceArray;
  18. import java.util.concurrent.atomic.LongAdder;
  19. import java.util.concurrent.locks.ReentrantLock;
  20. import org.eclipse.jgit.annotations.NonNull;
  21. import org.eclipse.jgit.internal.JGitText;
  22. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  23. import org.eclipse.jgit.storage.file.WindowCacheStats;
  24. import org.eclipse.jgit.util.Monitoring;
  25. /**
  26. * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in
  27. * memory for faster read access.
  28. * <p>
  29. * The WindowCache serves as a Java based "buffer cache", loading segments of a
  30. * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
  31. * only tiny slices of a file, the WindowCache tries to smooth out these tiny
  32. * reads into larger block-sized IO operations.
  33. * <p>
  34. * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by
  35. * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
  36. * This is ensured by an array of locks, with the tuple hashed to a lock
  37. * instance.
  38. * <p>
  39. * During a miss, older entries are evicted from the cache so long as
  40. * {@link #isFull()} returns true.
  41. * <p>
  42. * Its too expensive during object access to be 100% accurate with a least
  43. * recently used (LRU) algorithm. Strictly ordering every read is a lot of
  44. * overhead that typically doesn't yield a corresponding benefit to the
  45. * application.
  46. * <p>
  47. * This cache implements a loose LRU policy by randomly picking a window
  48. * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
  49. * within that window.
  50. * <p>
  51. * Entities created by the cache are held under SoftReferences, permitting the
  52. * Java runtime's garbage collector to evict entries when heap memory gets low.
  53. * Most JREs implement a loose least recently used algorithm for this eviction.
  54. * <p>
  55. * The internal hash table does not expand at runtime, instead it is fixed in
  56. * size at cache creation time. The internal lock table used to gate load
  57. * invocations is also fixed in size.
  58. * <p>
  59. * The key tuple is passed through to methods as a pair of parameters rather
  60. * than as a single Object, thus reducing the transient memory allocations of
  61. * callers. It is more efficient to avoid the allocation, as we can't be 100%
  62. * sure that a JIT would be able to stack-allocate a key tuple.
  63. * <p>
  64. * This cache has an implementation rule such that:
  65. * <ul>
  66. * <li>{@link #load(PackFile, long)} is invoked by at most one thread at a time
  67. * for a given <code>(PackFile,position)</code> tuple.</li>
  68. * <li>For every <code>load()</code> invocation there is exactly one
  69. * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a
  70. * SoftReference around the cached entity.</li>
  71. * <li>For every Reference created by <code>createRef()</code> there will be
  72. * exactly one call to {@link #clear(Ref)} to cleanup any resources associated
  73. * with the (now expired) cached entity.</li>
  74. * </ul>
  75. * <p>
  76. * Therefore, it is safe to perform resource accounting increments during the
  77. * {@link #load(PackFile, long)} or
  78. * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching
  79. * decrements during {@link #clear(Ref)}. Implementors may need to override
  80. * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional
  81. * accounting information into an implementation specific
  82. * {@link org.eclipse.jgit.internal.storage.file.WindowCache.Ref} subclass, as
  83. * the cached entity may have already been evicted by the JRE's garbage
  84. * collector.
  85. * <p>
  86. * To maintain higher concurrency workloads, during eviction only one thread
  87. * performs the eviction work, while other threads can continue to insert new
  88. * objects in parallel. This means that the cache can be temporarily over limit,
  89. * especially if the nominated eviction thread is being starved relative to the
  90. * other threads.
  91. */
  92. public class WindowCache {
  93. /**
  94. * Record statistics for a cache
  95. */
  96. static interface StatsRecorder {
  97. /**
  98. * Record cache hits. Called when cache returns a cached entry.
  99. *
  100. * @param count
  101. * number of cache hits to record
  102. */
  103. void recordHits(int count);
  104. /**
  105. * Record cache misses. Called when the cache returns an entry which had
  106. * to be loaded.
  107. *
  108. * @param count
  109. * number of cache misses to record
  110. */
  111. void recordMisses(int count);
  112. /**
  113. * Record a successful load of a cache entry
  114. *
  115. * @param loadTimeNanos
  116. * time to load a cache entry
  117. */
  118. void recordLoadSuccess(long loadTimeNanos);
  119. /**
  120. * Record a failed load of a cache entry
  121. *
  122. * @param loadTimeNanos
  123. * time used trying to load a cache entry
  124. */
  125. void recordLoadFailure(long loadTimeNanos);
  126. /**
  127. * Record cache evictions due to the cache evictions strategy
  128. *
  129. * @param count
  130. * number of evictions to record
  131. */
  132. void recordEvictions(int count);
  133. /**
  134. * Record files opened by cache
  135. *
  136. * @param count
  137. * delta of number of files opened by cache
  138. */
  139. void recordOpenFiles(int count);
  140. /**
  141. * Record cached bytes
  142. *
  143. * @param count
  144. * delta of cached bytes
  145. */
  146. void recordOpenBytes(int count);
  147. /**
  148. * Returns a snapshot of this recorder's stats. Note that this may be an
  149. * inconsistent view, as it may be interleaved with update operations.
  150. *
  151. * @return a snapshot of this recorder's stats
  152. */
  153. @NonNull
  154. WindowCacheStats getStats();
  155. }
  156. static class StatsRecorderImpl
  157. implements StatsRecorder, WindowCacheStats {
  158. private final LongAdder hitCount;
  159. private final LongAdder missCount;
  160. private final LongAdder loadSuccessCount;
  161. private final LongAdder loadFailureCount;
  162. private final LongAdder totalLoadTime;
  163. private final LongAdder evictionCount;
  164. private final LongAdder openFileCount;
  165. private final LongAdder openByteCount;
  166. /**
  167. * Constructs an instance with all counts initialized to zero.
  168. */
  169. public StatsRecorderImpl() {
  170. hitCount = new LongAdder();
  171. missCount = new LongAdder();
  172. loadSuccessCount = new LongAdder();
  173. loadFailureCount = new LongAdder();
  174. totalLoadTime = new LongAdder();
  175. evictionCount = new LongAdder();
  176. openFileCount = new LongAdder();
  177. openByteCount = new LongAdder();
  178. }
  179. @Override
  180. public void recordHits(int count) {
  181. hitCount.add(count);
  182. }
  183. @Override
  184. public void recordMisses(int count) {
  185. missCount.add(count);
  186. }
  187. @Override
  188. public void recordLoadSuccess(long loadTimeNanos) {
  189. loadSuccessCount.increment();
  190. totalLoadTime.add(loadTimeNanos);
  191. }
  192. @Override
  193. public void recordLoadFailure(long loadTimeNanos) {
  194. loadFailureCount.increment();
  195. totalLoadTime.add(loadTimeNanos);
  196. }
  197. @Override
  198. public void recordEvictions(int count) {
  199. evictionCount.add(count);
  200. }
  201. @Override
  202. public void recordOpenFiles(int count) {
  203. openFileCount.add(count);
  204. }
  205. @Override
  206. public void recordOpenBytes(int count) {
  207. openByteCount.add(count);
  208. }
  209. @Override
  210. public WindowCacheStats getStats() {
  211. return this;
  212. }
  213. @Override
  214. public long getHitCount() {
  215. return hitCount.sum();
  216. }
  217. @Override
  218. public long getMissCount() {
  219. return missCount.sum();
  220. }
  221. @Override
  222. public long getLoadSuccessCount() {
  223. return loadSuccessCount.sum();
  224. }
  225. @Override
  226. public long getLoadFailureCount() {
  227. return loadFailureCount.sum();
  228. }
  229. @Override
  230. public long getEvictionCount() {
  231. return evictionCount.sum();
  232. }
  233. @Override
  234. public long getTotalLoadTime() {
  235. return totalLoadTime.sum();
  236. }
  237. @Override
  238. public long getOpenFileCount() {
  239. return openFileCount.sum();
  240. }
  241. @Override
  242. public long getOpenByteCount() {
  243. return openByteCount.sum();
  244. }
  245. @Override
  246. public void resetCounters() {
  247. hitCount.reset();
  248. missCount.reset();
  249. loadSuccessCount.reset();
  250. loadFailureCount.reset();
  251. totalLoadTime.reset();
  252. evictionCount.reset();
  253. }
  254. }
  255. private static final int bits(int newSize) {
  256. if (newSize < 4096)
  257. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  258. if (Integer.bitCount(newSize) != 1)
  259. throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
  260. return Integer.numberOfTrailingZeros(newSize);
  261. }
  262. private static final Random rng = new Random();
  263. private static volatile WindowCache cache;
  264. private static volatile int streamFileThreshold;
  265. static {
  266. reconfigure(new WindowCacheConfig());
  267. }
  268. /**
  269. * Modify the configuration of the window cache.
  270. * <p>
  271. * The new configuration is applied immediately. If the new limits are
  272. * smaller than what is currently cached, older entries will be purged
  273. * as soon as possible to allow the cache to meet the new limit.
  274. *
  275. * @deprecated use {@code cfg.install()} to avoid internal reference.
  276. * @param cfg
  277. * the new window cache configuration.
  278. * @throws java.lang.IllegalArgumentException
  279. * the cache configuration contains one or more invalid
  280. * settings, usually too low of a limit.
  281. */
  282. @Deprecated
  283. public static void reconfigure(WindowCacheConfig cfg) {
  284. final WindowCache nc = new WindowCache(cfg);
  285. final WindowCache oc = cache;
  286. if (oc != null)
  287. oc.removeAll();
  288. cache = nc;
  289. streamFileThreshold = cfg.getStreamFileThreshold();
  290. DeltaBaseCache.reconfigure(cfg);
  291. }
  292. static int getStreamFileThreshold() {
  293. return streamFileThreshold;
  294. }
  295. /**
  296. * @return the cached instance.
  297. */
  298. public static WindowCache getInstance() {
  299. return cache;
  300. }
  301. static final ByteWindow get(PackFile pack, long offset)
  302. throws IOException {
  303. final WindowCache c = cache;
  304. final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
  305. if (c != cache) {
  306. // The cache was reconfigured while we were using the old one
  307. // to load this window. The window is still valid, but our
  308. // cache may think its still live. Ensure the window is removed
  309. // from the old cache so resources can be released.
  310. //
  311. c.removeAll();
  312. }
  313. return r;
  314. }
  315. static final void purge(PackFile pack) {
  316. cache.removeAll(pack);
  317. }
  318. /** ReferenceQueue to cleanup released and garbage collected windows. */
  319. private final ReferenceQueue<ByteWindow> queue;
  320. /** Number of entries in {@link #table}. */
  321. private final int tableSize;
  322. /** Access clock for loose LRU. */
  323. private final AtomicLong clock;
  324. /** Hash bucket directory; entries are chained below. */
  325. private final AtomicReferenceArray<Entry> table;
  326. /** Locks to prevent concurrent loads for same (PackFile,position). */
  327. private final Lock[] locks;
  328. /** Lock to elect the eviction thread after a load occurs. */
  329. private final ReentrantLock evictLock;
  330. /** Number of {@link #table} buckets to scan for an eviction window. */
  331. private final int evictBatch;
  332. private final int maxFiles;
  333. private final long maxBytes;
  334. private final boolean mmap;
  335. private final int windowSizeShift;
  336. private final int windowSize;
  337. private final StatsRecorder statsRecorder;
  338. private final StatsRecorderImpl mbean;
  339. private WindowCache(WindowCacheConfig cfg) {
  340. tableSize = tableSize(cfg);
  341. final int lockCount = lockCount(cfg);
  342. if (tableSize < 1)
  343. throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
  344. if (lockCount < 1)
  345. throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
  346. queue = new ReferenceQueue<>();
  347. clock = new AtomicLong(1);
  348. table = new AtomicReferenceArray<>(tableSize);
  349. locks = new Lock[lockCount];
  350. for (int i = 0; i < locks.length; i++)
  351. locks[i] = new Lock();
  352. evictLock = new ReentrantLock();
  353. int eb = (int) (tableSize * .1);
  354. if (64 < eb)
  355. eb = 64;
  356. else if (eb < 4)
  357. eb = 4;
  358. if (tableSize < eb)
  359. eb = tableSize;
  360. evictBatch = eb;
  361. maxFiles = cfg.getPackedGitOpenFiles();
  362. maxBytes = cfg.getPackedGitLimit();
  363. mmap = cfg.isPackedGitMMAP();
  364. windowSizeShift = bits(cfg.getPackedGitWindowSize());
  365. windowSize = 1 << windowSizeShift;
  366. mbean = new StatsRecorderImpl();
  367. statsRecorder = mbean;
  368. Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
  369. if (maxFiles < 1)
  370. throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
  371. if (maxBytes < windowSize)
  372. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  373. }
  374. /**
  375. * @return cache statistics for the WindowCache
  376. */
  377. public WindowCacheStats getStats() {
  378. return statsRecorder.getStats();
  379. }
  380. /**
  381. * Reset stats. Does not reset open bytes and open files stats.
  382. */
  383. public void resetStats() {
  384. mbean.resetCounters();
  385. }
  386. private int hash(int packHash, long off) {
  387. return packHash + (int) (off >>> windowSizeShift);
  388. }
  389. private ByteWindow load(PackFile pack, long offset) throws IOException {
  390. long startTime = System.nanoTime();
  391. if (pack.beginWindowCache())
  392. statsRecorder.recordOpenFiles(1);
  393. try {
  394. if (mmap)
  395. return pack.mmap(offset, windowSize);
  396. ByteArrayWindow w = pack.read(offset, windowSize);
  397. statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
  398. return w;
  399. } catch (IOException | RuntimeException | Error e) {
  400. close(pack);
  401. statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
  402. throw e;
  403. } finally {
  404. statsRecorder.recordMisses(1);
  405. }
  406. }
  407. private Ref createRef(PackFile p, long o, ByteWindow v) {
  408. final Ref ref = new Ref(p, o, v, queue);
  409. statsRecorder.recordOpenBytes(ref.size);
  410. return ref;
  411. }
  412. private void clear(Ref ref) {
  413. statsRecorder.recordOpenBytes(-ref.size);
  414. statsRecorder.recordEvictions(1);
  415. close(ref.pack);
  416. }
  417. private void close(PackFile pack) {
  418. if (pack.endWindowCache()) {
  419. statsRecorder.recordOpenFiles(-1);
  420. }
  421. }
  422. private boolean isFull() {
  423. return maxFiles < mbean.getOpenFileCount()
  424. || maxBytes < mbean.getOpenByteCount();
  425. }
  426. private long toStart(long offset) {
  427. return (offset >>> windowSizeShift) << windowSizeShift;
  428. }
  429. private static int tableSize(WindowCacheConfig cfg) {
  430. final int wsz = cfg.getPackedGitWindowSize();
  431. final long limit = cfg.getPackedGitLimit();
  432. if (wsz <= 0)
  433. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  434. if (limit < wsz)
  435. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  436. return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
  437. }
  438. private static int lockCount(WindowCacheConfig cfg) {
  439. return Math.max(cfg.getPackedGitOpenFiles(), 32);
  440. }
  441. /**
  442. * Lookup a cached object, creating and loading it if it doesn't exist.
  443. *
  444. * @param pack
  445. * the pack that "contains" the cached object.
  446. * @param position
  447. * offset within <code>pack</code> of the object.
  448. * @return the object reference.
  449. * @throws IOException
  450. * the object reference was not in the cache and could not be
  451. * obtained by {@link #load(PackFile, long)}.
  452. */
  453. private ByteWindow getOrLoad(PackFile pack, long position)
  454. throws IOException {
  455. final int slot = slot(pack, position);
  456. final Entry e1 = table.get(slot);
  457. ByteWindow v = scan(e1, pack, position);
  458. if (v != null) {
  459. statsRecorder.recordHits(1);
  460. return v;
  461. }
  462. synchronized (lock(pack, position)) {
  463. Entry e2 = table.get(slot);
  464. if (e2 != e1) {
  465. v = scan(e2, pack, position);
  466. if (v != null) {
  467. statsRecorder.recordHits(1);
  468. return v;
  469. }
  470. }
  471. v = load(pack, position);
  472. final Ref ref = createRef(pack, position, v);
  473. hit(ref);
  474. for (;;) {
  475. final Entry n = new Entry(clean(e2), ref);
  476. if (table.compareAndSet(slot, e2, n))
  477. break;
  478. e2 = table.get(slot);
  479. }
  480. }
  481. if (evictLock.tryLock()) {
  482. try {
  483. gc();
  484. evict();
  485. } finally {
  486. evictLock.unlock();
  487. }
  488. }
  489. return v;
  490. }
  491. private ByteWindow scan(Entry n, PackFile pack, long position) {
  492. for (; n != null; n = n.next) {
  493. final Ref r = n.ref;
  494. if (r.pack == pack && r.position == position) {
  495. final ByteWindow v = r.get();
  496. if (v != null) {
  497. hit(r);
  498. return v;
  499. }
  500. n.kill();
  501. break;
  502. }
  503. }
  504. return null;
  505. }
  506. private void hit(Ref r) {
  507. // We don't need to be 100% accurate here. Its sufficient that at least
  508. // one thread performs the increment. Any other concurrent access at
  509. // exactly the same time can simply use the same clock value.
  510. //
  511. // Consequently we attempt the set, but we don't try to recover should
  512. // it fail. This is why we don't use getAndIncrement() here.
  513. //
  514. final long c = clock.get();
  515. clock.compareAndSet(c, c + 1);
  516. r.lastAccess = c;
  517. }
  518. private void evict() {
  519. while (isFull()) {
  520. int ptr = rng.nextInt(tableSize);
  521. Entry old = null;
  522. int slot = 0;
  523. for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
  524. if (tableSize <= ptr)
  525. ptr = 0;
  526. for (Entry e = table.get(ptr); e != null; e = e.next) {
  527. if (e.dead)
  528. continue;
  529. if (old == null || e.ref.lastAccess < old.ref.lastAccess) {
  530. old = e;
  531. slot = ptr;
  532. }
  533. }
  534. }
  535. if (old != null) {
  536. old.kill();
  537. gc();
  538. final Entry e1 = table.get(slot);
  539. table.compareAndSet(slot, e1, clean(e1));
  540. }
  541. }
  542. }
  543. /**
  544. * Clear every entry from the cache.
  545. * <p>
  546. * This is a last-ditch effort to clear out the cache, such as before it
  547. * gets replaced by another cache that is configured differently. This
  548. * method tries to force every cached entry through {@link #clear(Ref)} to
  549. * ensure that resources are correctly accounted for and cleaned up by the
  550. * subclass. A concurrent reader loading entries while this method is
  551. * running may cause resource accounting failures.
  552. */
  553. private void removeAll() {
  554. for (int s = 0; s < tableSize; s++) {
  555. Entry e1;
  556. do {
  557. e1 = table.get(s);
  558. for (Entry e = e1; e != null; e = e.next)
  559. e.kill();
  560. } while (!table.compareAndSet(s, e1, null));
  561. }
  562. gc();
  563. }
  564. /**
  565. * Clear all entries related to a single file.
  566. * <p>
  567. * Typically this method is invoked during {@link PackFile#close()}, when we
  568. * know the pack is never going to be useful to us again (for example, it no
  569. * longer exists on disk). A concurrent reader loading an entry from this
  570. * same pack may cause the pack to become stuck in the cache anyway.
  571. *
  572. * @param pack
  573. * the file to purge all entries of.
  574. */
  575. private void removeAll(PackFile pack) {
  576. for (int s = 0; s < tableSize; s++) {
  577. final Entry e1 = table.get(s);
  578. boolean hasDead = false;
  579. for (Entry e = e1; e != null; e = e.next) {
  580. if (e.ref.pack == pack) {
  581. e.kill();
  582. hasDead = true;
  583. } else if (e.dead)
  584. hasDead = true;
  585. }
  586. if (hasDead)
  587. table.compareAndSet(s, e1, clean(e1));
  588. }
  589. gc();
  590. }
  591. private void gc() {
  592. Ref r;
  593. while ((r = (Ref) queue.poll()) != null) {
  594. clear(r);
  595. final int s = slot(r.pack, r.position);
  596. final Entry e1 = table.get(s);
  597. for (Entry n = e1; n != null; n = n.next) {
  598. if (n.ref == r) {
  599. n.dead = true;
  600. table.compareAndSet(s, e1, clean(e1));
  601. break;
  602. }
  603. }
  604. }
  605. }
  606. private int slot(PackFile pack, long position) {
  607. return (hash(pack.hash, position) >>> 1) % tableSize;
  608. }
  609. private Lock lock(PackFile pack, long position) {
  610. return locks[(hash(pack.hash, position) >>> 1) % locks.length];
  611. }
  612. private static Entry clean(Entry top) {
  613. while (top != null && top.dead) {
  614. top.ref.enqueue();
  615. top = top.next;
  616. }
  617. if (top == null)
  618. return null;
  619. final Entry n = clean(top.next);
  620. return n == top.next ? top : new Entry(n, top.ref);
  621. }
  622. private static class Entry {
  623. /** Next entry in the hash table's chain list. */
  624. final Entry next;
  625. /** The referenced object. */
  626. final Ref ref;
  627. /**
  628. * Marked true when ref.get() returns null and the ref is dead.
  629. * <p>
  630. * A true here indicates that the ref is no longer accessible, and that
  631. * we therefore need to eventually purge this Entry object out of the
  632. * bucket's chain.
  633. */
  634. volatile boolean dead;
  635. Entry(Entry n, Ref r) {
  636. next = n;
  637. ref = r;
  638. }
  639. final void kill() {
  640. dead = true;
  641. ref.enqueue();
  642. }
  643. }
  644. /** A soft reference wrapped around a cached object. */
  645. private static class Ref extends SoftReference<ByteWindow> {
  646. final PackFile pack;
  647. final long position;
  648. final int size;
  649. long lastAccess;
  650. protected Ref(final PackFile pack, final long position,
  651. final ByteWindow v, final ReferenceQueue<ByteWindow> queue) {
  652. super(v, queue);
  653. this.pack = pack;
  654. this.position = position;
  655. this.size = v.size();
  656. }
  657. }
  658. private static final class Lock {
  659. // Used only for its implicit monitor.
  660. }
  661. }