Package org.jnetpcap.nio

Source Code of org.jnetpcap.nio.DisposableGC

/*
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Sly Technologies, Inc.
*
* This file is part of jNetPcap.
*
* jNetPcap is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.jnetpcap.nio;

import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.sql.Time;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.jnetpcap.util.Units;

// TODO: Auto-generated Javadoc
/**
* Specialized garbage-collector that invokes the.
*
* {@link DisposableReference#dispose} method immediately as soon as a
* DisposableReference becomes unreferancable and put on the main garbage
* collector's list.
*
* @author markbe
*/
public final class DisposableGC {

  /** The Constant DEFAULT_CLEANUP_THREAD_TIMEOUT. */
  private static final long DEFAULT_CLEANUP_THREAD_TIMEOUT = 20;

  /** The default gc. */
  private static DisposableGC defaultGC = new DisposableGC();

  /** The Constant G10. */
  private static final long G10 = 10 * 1000;

  /** The Constant G60. */
  private static final long G60 = 60 * 1000;

  /** The Constant MANUAL_DRAING_MAX. */
  static final private int MANUAL_DRAING_MAX = 2;

  /**
   * When maxDirectMemorySize is breached, this is the minimum amount of memory
   * to release, triggering a System.gc() if necessary.
   */
  static final int MIN_MEMORY_RELEASE = 2 * Units.MEBIBYTE;

  /**
   * Minimum delay before 2 consecutive System.gc calls can be made
   */
  static final long MIN_SYSTEM_GC_INVOKE_TIMEOUT = 200;

  /** The Constant OUT_OF_MEMORY_TIMEOUT. */
  static final long OUT_OF_MEMORY_TIMEOUT = 15 * 1000;

  /**
   * Gets the default.
   *
   * @return the default
   */
  public static DisposableGC getDefault() {
    return defaultGC;
  }

  /**
   * Mem.
   *
   * @param c
   *          the c
   * @return the long
   */
  private static long mem(LinkSequence<DisposableReference> c) {
    long size = 0;
    for (DisposableReference ref : c) {
      size += ref.size();
    }

    return size;
  }

  /** The cleanup thread. */
  private Thread cleanupThread;

  /** The cleanup thread active. */
  private final AtomicBoolean cleanupThreadActive = new AtomicBoolean(false);

  /** The cleanup thread processing. */
  private final AtomicBoolean cleanupThreadProcessing =
      new AtomicBoolean(false);

  /** The cleanup timeout. */
  private final AtomicLong cleanupTimeout = new AtomicLong(
      DisposableGC.DEFAULT_CLEANUP_THREAD_TIMEOUT);

  /** The delta count. */
  private long deltaCount;

  /** The delta size. */
  private long deltaSize;
  /**
   * Performance in 1000s of pps using various collection types:
   *
   * <pre>
   * Type           Threaded   Non-Threaded
   *                 min-max     min-max
   * ------------------------------------------------   
   * ArrayDeque:    43.3-44.8   42.5-44.7
   * ArrayList:     43.2-44.7   42.9-45.0
   * LinkedList:    43.7-44.8   42.6-44.5
   * HashSet:       43.3-44.4   41.2-43.5
   * LinkedHashSet: 42.4-43.5   43.2-44.3
   * </pre>
   */
  // final Collection<DisposableReference> refCollection3 =
  // new ArrayDeque<DisposableReference>(20000);
  // new ArrayList<DisposableReference>(20000);
  // new LinkedList<DisposableReference>();
  // new HashSet<DisposableReference>(20000);
  // new LinkedHashSet<DisposableReference>(20000);

  final LinkSequence<DisposableReference> g0 =
      new LinkSequence<DisposableReference>("g0");

  /** The g10. */
  final LinkSequence<DisposableReference> g10 =
      new LinkSequence<DisposableReference>("g10");

  /** The g60. */
  final LinkSequence<DisposableReference> g60 =
      new LinkSequence<DisposableReference>("g60");

  /** The last system gc invoke. */
  private long lastSystemGCInvoke = 0;

  /** The first system gc needed. */
  private long firstSystemGCNeeded = 0;

  /*
   * private static class Marker extends PhantomReference<Object> {
   *
   * @SuppressWarnings("unused") public final long id;
   *//** The marker queue. */
  /*
   * public Marker(long id) { super(new Object() { },
   * DisposableGC.getDeault().markerQueue);
   *
   * this.id = id; }
   *
   * }
   */
  final ReferenceQueue<Object> markerQueue = new ReferenceQueue<Object>();

  /** The marker reference. */
  private Reference<Object> markerReference;

  /** The memory semaphore. */
  private final Semaphore memorySemaphore = new Semaphore(
      DisposableGC.MIN_MEMORY_RELEASE);

  /** The ref queue. */
  final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

  /** The total disposed. */
  private long totalDisposed = 1;

  /** The total size. */
  private long totalSize;

  /** The verbose. */
  private boolean verbose = false;

  /** A bit more verbose. */
  private boolean vverbose = false;

  /** The vvverbose. */
  private boolean vvverbose = false;

  /**
   * Instantiates a new disposable gc.
   */
  private DisposableGC() {
    startCleanupThread();

    try {
      setVerbose(Boolean.parseBoolean(System
          .getProperty("nio.verbose", "false")));
      setVVerbose(Boolean.parseBoolean(System.getProperty("nio.vverbose",
          "false")));
      setVVVerbose(Boolean.parseBoolean(System.getProperty("nio.vvverbose",
          "false")));
    } catch (Exception e) {
      // Ignore any formatting exceptions from the command line
    }
  }

  /**
   * Dispose.
   *
   * @param ref
   *          the ref
   */
  private void dispose(DisposableReference ref) {

    synchronized (g0) {
      memorySemaphore.release(ref.size());

      totalDisposed++;
      totalSize += ref.size();
      ref.dispose();
      ref.remove();

      if (g0.isEmpty() && g10.isEmpty() && g60.isEmpty()) {
        g0.notifyAll();
      }
    }

  }

  /**
   * Drain ref queue.
   */
  public void drainRefQueue() {
    while (true) {
      DisposableReference ref = (DisposableReference) refQueue.poll();
      if (ref == null) {
        break;
      }

      dispose(ref);
    }
  }

  /**
   * Drain ref queue.
   *
   * @param timeout
   *          the timeout
   * @throws IllegalArgumentException
   *           the illegal argument exception
   * @throws InterruptedException
   *           the interrupted exception
   */
  public void drainRefQueue(long timeout) throws IllegalArgumentException,
      InterruptedException {

    memorySemaphore.acquire(memorySemaphore.availablePermits()); // Grab all
    // of
    // them

    /*
     * Breakup the timeout into 100 partitions so that we can check the permits
     * more often then just a single monolithic times.
     */
    long partition = timeout / 100;
    while (memorySemaphore.availablePermits() < MIN_MEMORY_RELEASE) {
      DisposableReference ref = (DisposableReference) refQueue.remove(timeout);

      if (ref == null && partition++ < 100) {
        continue;
      }

      if (ref == null) {
        break;
      }

      dispose(ref);
    }
  }

  /**
   * Drain ref queue bounded.
   */
  void drainRefQueueBounded() {
    int iterations = 0;
    while (iterations < MANUAL_DRAING_MAX) {
      DisposableReference ref = (DisposableReference) refQueue.poll();
      if (ref == null) {
        break;
      }

      dispose(ref);
      ++iterations;
    }
  }

  /**
   * Drain ref queue loop.
   *
   * @throws InterruptedException
   *           the interrupted exception
   */
  void drainRefQueueLoop() throws InterruptedException {

    deltaCount = 0;
    deltaSize = 0;
    final long timeout = cleanupTimeout.get();
    long ts = System.currentTimeMillis();
    while (true) {

      final DisposableReference ref =
          (DisposableReference) refQueue.remove(timeout);

      if (ref != null) { // We have a reference to dispose of
        if (deltaCount == 0) { // First one
          if (vvverbose && cleanupThreadProcessing.get() == false) {
            logBusy();
          }
          cleanupThreadProcessing.set(true);
          synchronized (cleanupThreadProcessing) {
            cleanupThreadProcessing.notifyAll(); // Signal start
          }
        }
        deltaCount++;
        deltaSize += ref.size();

        /**
         * Keep message coming even if we are continuously processing.
         */
        if (vverbose && (deltaCount % 10000) == 0) {
          sortGenerations();
          logUsage();
        }

        /*
         * Means, we just finished processing
         */
      } else if (deltaCount != 0 && (System.currentTimeMillis() - ts) >= 1000) {
        ts = System.currentTimeMillis();
        sortGenerations();
        if (verbose && deltaCount > 00) {
          logUsage();
        }

        deltaCount = 0;
        deltaSize = 0;
        cleanupThreadProcessing.set(false);

        synchronized (cleanupThreadProcessing) { // Signal finish
          cleanupThreadProcessing.notifyAll();
        }
        if (vvverbose) {
          logIdle();
        }
      }

      if (ref == null) {
        if (cleanupThreadActive.get()) {
          if (memorySemaphore.hasQueuedThreads()) {
            invokeSystemGC();
          }

          continue; // Null due to timeout
        } else {
          if (verbose) {
            logFinished();
          }
          break;
        }
      }

      dispose(ref);
    }

    if (verbose && deltaCount != 0) {
      System.out.printf("DisposableGC: disposed of %d entries [total=%dM]%n",
          deltaCount,
          totalDisposed / 1000000);
      deltaCount = 0;
    }
  }

  /**
   * F.
   *
   * @param l
   *          the l
   * @return the string
   */
  private String f(long l) {
    return f(l, -1, "");
  }

  /**
   * F.
   *
   * @param l
   *          the l
   * @param percision
   *          the percision
   * @return the string
   */
  @SuppressWarnings("unused")
  private String f(long l, int percision) {
    return f(l, percision, "");
  }

  /**
   * F.
   *
   * @param l
   *          the l
   * @param percision
   *          the percision
   * @param post
   *          the post
   * @return the string
   */
  private String f(long l, int percision, String post) {
    String u = "";
    double v = l;
    int p = 0;
    if (l > Units.TEBIBYTE) {
      u = "t";
      v /= 3;
      p = 4;
    } else if (l > Units.GIGIBYTE) {
      u = "g";
      v /= Units.GIGIBYTE;
      p = 2;
    } else if (l > Units.MEBIBYTE) {
      u = "m";
      v /= Units.MEBIBYTE;
      p = 1;
    } else if (l > Units.KIBIBYTE) {
      u = "k";
      v /= Units.KIBIBYTE;
      p = 0;
    } else {
      p = 0;
    }

    if (percision != -1) {
      p = percision;
    }

    String f = String.format("%%.%df%%s%%s", p);

    return String.format(f, v, u, post);
  }

  /**
   * Fb.
   *
   * @param l
   *          the l
   * @return the string
   */
  private String fb(long l) {
    return f(l, -1, "b");
  }

  /**
   * Fb.
   *
   * @param l
   *          the l
   * @param percision
   *          the percision
   * @return the string
   */
  private String fb(long l, int percision) {
    return f(l, percision, "b");
  }

  /**
   * Gets the cleanup thread timeout.
   *
   * @return the cleanup thread timeout
   */
  public long getCleanupThreadTimeout() {
    return cleanupTimeout.get();
  }

  /**
   * Makes sure that JVM GC is not invoked more then a certain timeout value
   * since the last time it was invoked. Avoids too many JVM GC invocation calls
   * that might overlap
   *
   * @return true if JVM GC was invoked, otherwise false
   */
  private boolean invokeSystemGC() {

    if ((System.currentTimeMillis() - lastSystemGCInvoke) < MIN_SYSTEM_GC_INVOKE_TIMEOUT) {
      return false;
    }

    if (vverbose) {
      logSystemGC();
    }

    System.gc();
    lastSystemGCInvoke = System.currentTimeMillis();
    firstSystemGCNeeded = 0;

    return true;
  }

  /*
   * private long systemMinorGC() {
   *
   * final long timestamp = System.currentTimeMillis(); Marker marker = new
   * Marker(timestamp); // Now we wait for Marker to be
   *
   * filler = 1;
   *
   * while (true) { final Marker mark = (Marker) markerQueue.poll();
   *
   * if (mark == marker) { while (markerQueue.poll() != null) { ; // Drain the
   * queue quickly } break; } else {
   *
   * filler++; new Object() { };
   *
   * // Thread.yield(); try { if (filler % 10000 == 0) { Thread.sleep(1); } }
   * catch (InterruptedException e) { } } }
   *
   * return filler;
   *
   * }
   */
  /**
   * Invoke system gc and wait.
   */
  public synchronized void invokeSystemGCAndWait() {

    long ts = System.currentTimeMillis();
    long low = JMemory.availableDirectMemory();

    try {
      if (isCleanupThreadActive()) {
        memorySemaphore.acquire(memorySemaphore.availablePermits());
        memorySemaphore.tryAcquire(MIN_MEMORY_RELEASE,
            OUT_OF_MEMORY_TIMEOUT,
            TimeUnit.MILLISECONDS);
      } else {
        invokeSystemGC();
        drainRefQueue(OUT_OF_MEMORY_TIMEOUT);
      }
    } catch (IllegalArgumentException e) {
    } catch (InterruptedException e) {
    }

    if (vverbose) {
      System.out.printf("DisposableGC: waiting for System.gc to finish:"
          + " %dms, freed=%dMbytes%n",
          (System.currentTimeMillis() - ts),
          (JMemory.availableDirectMemory() - low) / (1024 * 1024));
    }
  }

  /**
   * Issues a JVM GC request, while injecting a marker reference to be cleaned
   * up by the very same JVM GC run. Until our marker reference is not cleaned
   * up, we do not issue another JVM GC since this means that previous GC run
   * has not reached our marker reference yet.
   */
  public synchronized void invokeSystemGCWithMarker() {

    if (markerReference != null && markerReference.get() != null
        || !isSystemGCReady()) {
      return;
    }

    if (vvverbose) {
      logMarker();
    }
    markerReference = new WeakReference<Object>(new Object() {
    });
    invokeSystemGC();
  }

  /**
   * Checks if is cleanup complete.
   *
   * @return true, if is cleanup complete
   */
  public boolean isCleanupComplete() {
    synchronized (g0) {
      return g0.isEmpty();
    }
  }

  /**
   * Checks if is cleanup thread active.
   *
   * @return true, if is cleanup thread active
   */
  public boolean isCleanupThreadActive() {
    return cleanupThreadActive.get() && cleanupThread.isAlive();
  }

  /**
   * Checks if JVM GC can be called upon, at this particular time. If the
   * previous invocation of JVM GC was less then minimum delay between
   * consecutive calls, this function returns false.
   *
   * @return true if JVM GC can be invoked at this time, otherwise false
   */
  private final boolean isSystemGCReady() {
    if (firstSystemGCNeeded == 0) {
      firstSystemGCNeeded = System.currentTimeMillis();
    }

    return cleanupThreadProcessing.get() == false
        && (System.currentTimeMillis() - lastSystemGCInvoke) > MIN_SYSTEM_GC_INVOKE_TIMEOUT;
  }

  /**
   * Checks if is verbose.
   *
   * @return the verbose
   */
  public boolean isVerbose() {
    return verbose;
  }

  /**
   * Checks if is v verbose.
   *
   * @return the vverbose
   */
  public boolean isVVerbose() {
    return vverbose;
  }

  /**
   * Checks if is vV verbose.
   *
   * @return the vvverbose
   */
  public boolean isVVVerbose() {
    return vvverbose;
  }

  /**
   * Log busy.
   */
  private void logBusy() {
    System.out.printf("DisposableGC: busy%n");
  }

  /**
   * Log finished.
   */
  private void logFinished() {
    System.out.printf("DisposableGC: finished%n");
  }

  /**
   * Log idle.
   */
  private void logIdle() {
    System.out.printf("DisposableGC: idle - "
        + "waiting for system GC to collect more objects%n");

  }

  /**
   * Log limits.
   */
  private void logLimits() {
    System.out
        .printf("DisposableGC: current native memory allocation limits are max=%s, soft=%s%n",
            fb(JMemory.maxDirectMemory()),
            fb(JMemory.softDirectMemory()));

  }

  /**
   * Log marker.
   */
  private void logMarker() {
    long ts = System.currentTimeMillis();
    long fs = ts - (ts / 1000) * 1000;

    /*
     * Provide TS to fraction of a second in millis
     */
    System.out.printf("DisposableGC: soft limit breached, "
        + "issued a marker at %s.%d, minimum delay=%dms%n",
        new Time(ts),
        fs,
        MIN_SYSTEM_GC_INVOKE_TIMEOUT);

  }

  /**
   * Log system gc.
   */
  private void logSystemGC() {
    long ts = System.currentTimeMillis();
    long fs = ts - (ts / 1000) * 1000;
    long waited = ts - firstSystemGCNeeded;

    System.out
        .printf("DisposableGC: issued JVM GC request %s.%d waited=%dms (reserved=%s, available=%s)%n",
            new Time(ts),
            fs,
            waited,
            fb(JMemory.reservedDirectMemory()),
            fb(JMemory.availableDirectMemory()));

  }

  /**
   * Log usage.
   */
  private void logUsage() {
    System.out.printf("DisposableGC: [immediate=%3s(%4s)]=%3s(%7s) "
        + "[0sec=%3s(%6s),10sec=%3s(%6s),60sec=%3s(%6s)]=%6s%n",
        f(deltaCount),
        fb(deltaSize, 0),
        f(totalDisposed),
        fb(totalSize),
        f(g0.size()),
        fb(mem(g0)),
        f(g10.size()),
        fb(mem(g10)),
        f(g60.size()),
        fb(mem(g60)),
        fb(memoryHeldInRefCollection()));

  }

  /**
   * Memory held in ref collection.
   *
   * @return the long
   */
  private long memoryHeldInRefCollection() {
    long size = 0;

    size += mem(g0);
    size += mem(g10);
    size += mem(g60);

    return size;
  }

  /**
   * Sets the cleanup thread timeout.
   *
   * @param timeout
   *          the new cleanup thread timeout
   */
  public void setCleanupThreadTimeout(long timeout) {
    cleanupTimeout.set(timeout);
  }

  /**
   * Sets the verbose.
   *
   * @param verbose
   *          the verbose to set
   */
  public void setVerbose(boolean verbose) {
    this.verbose = verbose;

    if (!verbose) {
      setVVerbose(false);
      setVVVerbose(false);
    } else {
      logLimits();
    }

  }

  /**
   * Sets the v verbose.
   *
   * @param vverbose
   *          the vverbose to set
   */
  public void setVVerbose(boolean vverbose) {
    if (vverbose) {
      setVerbose(true);
    } else {
      setVVVerbose(false);
    }

    this.vverbose = vverbose;
  }

  /**
   * Sets the vV verbose.
   *
   * @param vvverbose
   *          the vvverbose to set
   */
  public void setVVVerbose(boolean vvverbose) {
    if (vvverbose) {
      setVVerbose(true);
    }
    this.vvverbose = vvverbose;
  }

  /**
   * Sort generations.
   */
  private void sortGenerations() {
    final long ct = System.currentTimeMillis();

    /*
     * Check for G60(64 second) old generation
     */
    for (DisposableReference ref : this.g10) {
      if ((ct - ref.getTs()) > G60) {
        g10.remove(ref);
        g60.add(ref);
      } else {
        break;
      }
    }

    /*
     * Check for G10 (10 second) old generation
     */
    for (DisposableReference ref : this.g0) {
      if ((ct - ref.getTs()) > G10) {
        g0.remove(ref);
        g10.add(ref);

        // System.out.printf("DisposableGC:: %s%n", ref);
      } else {
        break;
      }
      // System.out.printf("DisposableGC:: delta=%d%n", (ct - ref.getTs()));
    }
  }

  /**
   * Start cleanup thread.
   */
  public synchronized void startCleanupThread() {
    if (isCleanupThreadActive()) {
      return;
    }

    cleanupThread = new Thread(new Runnable() {

      public void run() {
        try {
          drainRefQueueLoop();

        } catch (InterruptedException e) {
          UncaughtExceptionHandler handler;
          handler = Thread.getDefaultUncaughtExceptionHandler();
          handler.uncaughtException(Thread.currentThread(), e);

        } finally {
          cleanupThreadActive.set(false);
          cleanupThread = null;

          synchronized (this) {
            notifyAll();
          }
        }
      }

    }, "DisposableGC");

    cleanupThreadActive.set(true);

    cleanupThread.setDaemon(true);
    cleanupThread.setPriority(cleanupThread.getPriority() - 1); // Lower
    // priority
    cleanupThread.start();
  }

  /**
   * Stop cleanup thread.
   *
   * @throws InterruptedException
   *           the interrupted exception
   */
  public void stopCleanupThread() throws InterruptedException {
    if (isCleanupThreadActive()) {
      synchronized (cleanupThread) {
        cleanupThreadActive.set(false);

        if (cleanupThread != null) {
          cleanupThread.wait();
        }
      }
    }
  }

  /**
   * Wait for forcable cleanup.
   *
   * @throws InterruptedException
   *           the interrupted exception
   */
  public void waitForForcableCleanup() throws InterruptedException {
    System.gc();
    while (waitForFullCleanup(5 * 1000) == false) {
      if (verbose && !cleanupThreadProcessing.get()) {
        System.out.printf("DisposableGC: waiting on %d elements%n", g0.size());
        for (int i = 0; i < g0.size(); i++) {
          DisposableReference o = g0.get(i);
          if (o != null && o.get() != null) {
            System.out.printf("DisposableGC:#%d: %s%n", i, o.get());
          }
        }
      }
    }

  }

  /**
   * Wait for forcable cleanup.
   *
   * @param timeout
   *          the timeout
   * @return true, if successful
   * @throws InterruptedException
   *           the interrupted exception
   */
  public boolean waitForForcableCleanup(long timeout)
      throws InterruptedException {
    int count = (int) (timeout / 100) + 1;
    while ((count-- >= 0) && waitForFullCleanup(100) == false) {
      invokeSystemGC();
    }

    return isCleanupComplete();
  }

  /**
   * Wait for full cleanup.
   *
   * @throws InterruptedException
   *           the interrupted exception
   */
  public void waitForFullCleanup() throws InterruptedException {

    synchronized (g0) {
      while (g0.isEmpty() == false) {
        if (isCleanupThreadActive()) {
          g0.wait();
        } else {
          drainRefQueue();
        }
      }
    }
  }

  /**
   * Wait for full cleanup.
   *
   * @param timeout
   *          the timeout
   * @return true, if successful
   * @throws InterruptedException
   *           the interrupted exception
   */
  public boolean waitForFullCleanup(long timeout) throws InterruptedException {

    synchronized (g0) {
      if (g0.isEmpty() == false) {
        if (isCleanupThreadActive()) {
          g0.wait(timeout);
        } else {
          drainRefQueue();
          if (g0.isEmpty() == false) {
            Thread.sleep(timeout);
            drainRefQueue();
          }
        }
      }

      return g0.isEmpty();
    }
  }
}
TOP

Related Classes of org.jnetpcap.nio.DisposableGC

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.