package fr.unistra.pelican.util.largeImages;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryNotificationInfo;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import fr.unistra.pelican.PelicanException;
/**
* The LargeImageMemoryManager follows the design pattern Singleton. It is used
* to manage the available memory of the JVM. It records all LargeImages that
* are created and choose which unit has to be discarded when memory goes low.
*
*/
public class LargeImageMemoryManager implements
javax.management.NotificationListener {
private static final LargeImageMemoryManager INSTANCE = new LargeImageMemoryManager();
/**
* Eviction List used to choose the next unit to be discarded
*/
private LinkedList<UnitRef> evictionList;
/**
* Memory Threshold use to launch a cleanup
*/
private long memoryThreshold;
/**
* Second memory Threshold used to stop new units from being created
*/
private long memoryThreshold2;
/**
* HashMap which associates an integer to each LargeImage that are created
*/
private HashMap<Integer, WeakReference<LargeImageInterface>> imageIndex;
/**
* MemoryPoolMXBean used to listen the memory
*/
private MemoryPoolMXBean tenured = null;
/**
* Lock used to make sure that two thread don't work in the evictionList at
* the same time
*/
public ReentrantLock lock;
/**
* Constructor
*/
private LargeImageMemoryManager() {
evictionList = new LinkedList<UnitRef>();
lock = new ReentrantLock();
imageIndex = new HashMap<Integer, WeakReference<LargeImageInterface>>();
List<MemoryPoolMXBean> bean = ManagementFactory.getMemoryPoolMXBeans();
for (MemoryPoolMXBean t : bean) {
if ((t.getType() == MemoryType.HEAP)
&& t.isUsageThresholdSupported()) {
tenured = t;
}
}
if (tenured == null) {
throw new PelicanException("Damn where is the tenured generation ?");
}
this.memoryThreshold = (long) (((double) tenured.getUsage().getMax()) * (1.0-LargeImageUtil.DEFAULT_MEMORY_FREE_RATIO));
this.memoryThreshold2 = (long) (((double) tenured.getUsage().getMax()) * (1.0-(LargeImageUtil.DEFAULT_MEMORY_FREE_RATIO/2)));
if(tenured.getUsage().getUsed()>this.memoryThreshold){
System.err.println("The MemoryManager was created in a low memory environment");
}
tenured.setUsageThreshold(this.memoryThreshold);
MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
NotificationEmitter emitter = (NotificationEmitter) mbean;
emitter.addNotificationListener(this, null, null);
}
/**
* Singleton getter.
*
* @return the instance of the MemoryManager
*/
public static LargeImageMemoryManager getInstance() {
return INSTANCE;
}
/**
* Method called when the memory goes low.
*/
public void handleNotification(Notification notification, Object arg1) {
String notifType = notification.getType();
if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
this.discard();
}
}
/**
* Checks if memory used has reach the second threshold and wait up to 20s if the free memory is too low.
*/
public void checkMemory() {
lock.lock();
try{
int lockCount = ((ReentrantLock)lock).getHoldCount();
if (tenured.getUsage().getUsed() > this.memoryThreshold2) {
for( int i = 0;i<20;i++){
if (tenured.getUsage().getUsed() < this.memoryThreshold){
break;
}else{
for (int j = 0; j<lockCount;j++){
lock.unlock();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j<lockCount;j++){
lock.lock();
}
}
if(i==20){
System.err.println("Someone waited 20s for the memory to lower");
}
}
}
}finally{
lock.unlock();
}
}
/**
* Discards the next 10 units in the evictionList to free memory until used
* memory goes under the first threshold.
*/
private void discard() {
lock.lock();
try{
UnitRef ref = null;
while (tenured.getUsage().getUsed() > this.memoryThreshold) {
for (int i = 0; i < LargeImageUtil.DEFAULT_DISCARD_NUMBER; i++) {
if ((ref = this.evictionList.pollFirst()) == null) {
System.err.println("Low memory and nothing to poll, we hope this is because the GC is on strike");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
} else {
LargeImageInterface currentImage = LargeImageMemoryManager.getInstance().imageIndex.get(ref.getImageId()).get();
if (currentImage != null) {
currentImage.discardUnit(ref.getUnitId());
}
}
}
System.gc();
}
}finally{
lock.unlock();
}
}
/**
* Indicates to the MemoryManager that an unit has been modified
*
* @param memoryId
* Index of the Image
* @param unitId
* Index of the Unit
*/
public void notifyUsage(int memoryId, int unitId) {
lock.lock();
try{
UnitRef currentUnit = new UnitRef(memoryId, unitId);
this.evictionList.remove(currentUnit);
this.evictionList.add(currentUnit);
}finally{
lock.unlock();
}
}
/**
* Allows to modify the MemoryFreeRatio
*
* @param newMemoryFreeRatio
*/
public void setMemoryFreeRatio(double newMemoryFreeRatio) {
this.memoryThreshold = (long) (Runtime.getRuntime().maxMemory() * newMemoryFreeRatio);
this.tenured.setUsageThreshold(this.memoryThreshold);
}
/**
* Gets the value of the first memory threshold.
*
* @return the first memory threshold.
*/
public long getMemoryThreshold() {
return this.memoryThreshold;
}
/**
* Adds a new Large Image to be managed
*
* @param img
* the new image to be managed
* @return the index where the Image has been recorded
*/
public int addImage(LargeImageInterface img) {
synchronized(this.imageIndex){
int result = this.imageIndex.size();
this.imageIndex.put(result, new WeakReference<LargeImageInterface>(img));
return result;
}
}
/**
* Closes all LargesImagesInterface registered to the Memory Manager. This
* method should be called
*/
public void closeAll() {
synchronized(this.imageIndex){
for (WeakReference<LargeImageInterface> weak : this.imageIndex.values()) {
LargeImageInterface largeIm = weak.get();
if (largeIm != null) {
largeIm.close();
}
}
}
}
/**
* Gets the max size of the Tenured generation.
* @return
* the max size of the Tenured generation
*/
public long getMaxTenuredMemory() {
return this.tenured.getUsage().getMax();
}
/**
* Class used to identify an unit in the MemoryManager. It stores the index
* of the Image from the imageIndex and the index of the unit in its image.
*/
public class UnitRef {
int imageId;
int unitId;
/**
* Constructor
*
* @param imageId
* Image index
* @param unitId
* Unit index
*/
public UnitRef(int imageId, int unitId) {
this.imageId = imageId;
this.unitId = unitId;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof UnitRef)) {
return false;
}
return (this.imageId == ((UnitRef) o).imageId)
&& (this.unitId == ((UnitRef) o).unitId);
}
@Override
public int hashCode() {
return ((Integer) this.imageId).hashCode();
}
/**
* Gets the Image index
*
* @return the Image index
*/
public int getImageId() {
return this.imageId;
}
/**
* Gets the unit index
*
* @return the unit index
*/
public int getUnitId() {
return this.unitId;
}
@Override
public String toString() {
return ("(" + this.imageId + "," + this.unitId + ")");
}
}
}