/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.internal.cache.lru; import org.apache.geode.StatisticDescriptor; import org.apache.geode.StatisticsFactory; import org.apache.geode.StatisticsType; import org.apache.geode.StatisticsTypeFactory; import org.apache.geode.cache.EvictionAction; import org.apache.geode.cache.EvictionAlgorithm; import org.apache.geode.cache.Region; import org.apache.geode.cache.util.ObjectSizer; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.internal.statistics.StatisticsTypeFactoryImpl; import org.apache.geode.internal.cache.*; import org.apache.geode.internal.cache.control.InternalResourceManager; import org.apache.geode.internal.i18n.LocalizedStrings; import java.util.Properties; /** * A <code>HeapLRUCapacityController</code> controls the contents of {@link Region} based on the * percentage of memory that is currently being used. If the percentage of memory in use exceeds the * given percentage, then the least recently used entry of the region is evicted. * * <P> * * For heap regions: GemStone has found that the <code>HeapLRUCapacityController</code> has the most * effect on a VM that is lauched with both the <code>-Xmx</code> and <code>-Xms</code> switches * used. Many virtual machine implementations have additional VM switches to control the behavior of * the garbage collector. We suggest that you investigate tuning the garbage collector when using a * <code>HeapLRUCapacityController</code>. In particular, we have found that when running with Sun's * <A href="http://java.sun.com/docs/hotspot/gc/index.html">HotSpot</a> VM, the * <code>-XX:+UseConcMarkSweepGC</code> and <code>-XX:+UseParNewGC</code> options improve the * behavior of the <code>HeapLRUCapacityController</code>. * * * @since GemFire 3.2 */ @SuppressWarnings("synthetic-access") public class HeapLRUCapacityController extends LRUAlgorithm { private static final long serialVersionUID = 4970685814429530675L; /** * The default percentage of VM heap usage over which LRU eviction occurs */ public static final String TOP_UP_HEAP_EVICTION_PERCENTAGE_PROPERTY = DistributionConfig.GEMFIRE_PREFIX + "topUpHeapEvictionPercentage"; public static final float DEFAULT_TOP_UP_HEAP_EVICTION_PERCENTAGE = 4.0f; public static final int DEFAULT_HEAP_PERCENTAGE = 75; public static final int PER_ENTRY_OVERHEAD = 250; private int perEntryOverhead = PER_ENTRY_OVERHEAD; /** * The default number of milliseconds the evictor thread should wait before evicting the LRU * entry. */ public static final int DEFAULT_EVICTOR_INTERVAL = 500; protected static final StatisticsType statType; static { // create the stats type for MemLRU. StatisticsTypeFactory f = StatisticsTypeFactoryImpl.singleton(); final String entryBytesDesc = "The amount of memory currently used by regions configured for eviction."; final String lruEvictionsDesc = "Number of total entry evictions triggered by LRU."; final String lruDestroysDesc = "Number of entries destroyed in the region through both destroy cache operations and eviction. Reset to zero each time it exceeds lruDestroysLimit."; final String lruDestroysLimitDesc = "Maximum number of entry destroys triggered by LRU before scan occurs."; final String lruEvaluationsDesc = "Number of entries evaluated during LRU operations."; final String lruGreedyReturnsDesc = "Number of non-LRU entries evicted during LRU operations"; statType = f.createType("HeapLRUStatistics", "Statistics about byte based Least Recently Used region entry disposal", new StatisticDescriptor[] {f.createLongGauge("entryBytes", entryBytesDesc, "bytes"), f.createLongCounter("lruEvictions", lruEvictionsDesc, "entries"), f.createLongCounter("lruDestroys", lruDestroysDesc, "entries"), f.createLongGauge("lruDestroysLimit", lruDestroysLimitDesc, "entries"), f.createLongCounter("lruEvaluations", lruEvaluationsDesc, "entries"), f.createLongCounter("lruGreedyReturns", lruGreedyReturnsDesc, "entries"),}); } // //////////////////// Instance Fields ///////////////////// // ////////////////////// Constructors /////////////////////// /** * Creates a new <code>HeapLRUCapacityController</code> with the given eviction action. * * @param evictionAction The action that will occur when an entry is evicted * */ public HeapLRUCapacityController(EvictionAction evictionAction, Region region) { super(evictionAction, region); } public HeapLRUCapacityController(ObjectSizer sizerImpl, EvictionAction evictionAction, Region region) { super(evictionAction, region); setSizer(sizerImpl); } // ///////////////////// Instance Methods /////////////////////// @Override public void setLimit(int maximum) {} // Candidate for removal since capacity controller is no longer part of // cache.xml @Override public Properties getProperties() { throw new IllegalStateException("Unused properties"); } @Override public void close() { super.close(); } @Override public long getLimit() { return 0; } @Override public boolean equals(Object cc) { if (!super.equals(cc)) return false; return true; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() * * Note that we just need to make sure that equal objects return equal hashcodes; nothing really * elaborate is done here. */ @Override public int hashCode() { int result = super.hashCode(); return result; } /** * Returns a brief description of this eviction controller. * * @since GemFire 4.0 */ @Override public String toString() { return LocalizedStrings.HeapLRUCapacityController_HEAPLRUCAPACITYCONTROLLER_WITH_A_CAPACITY_OF_0_OF_HEAP_AND_AN_THREAD_INTERVAL_OF_1_AND_EVICTION_ACTION_2 .toLocalizedString(new Object[] {Long.valueOf(this.getLimit()), this.getEvictionAction()}); } /** * Sets the {@link ObjectSizer} used to calculate the size of objects placed in the cache. * * @param sizer The name of the sizer class */ private void setSizer(ObjectSizer sizer) { this.sizer = sizer; } @Override protected EnableLRU createLRUHelper() { return new AbstractEnableLRU() { /** * Indicate what kind of <code>EvictionAlgorithm</code> this helper implements */ public EvictionAlgorithm getEvictionAlgorithm() { return EvictionAlgorithm.LRU_HEAP; } /** * As far as we're concerned all entries have the same size */ public int entrySize(Object key, Object value) throws IllegalArgumentException { // value is null only after eviction occurs. A change in size is // required for eviction stats, bug 30974 /* * if (value != null) { return 1; } else { return 0; } */ if (value == Token.TOMBSTONE) { return 0; } int size = HeapLRUCapacityController.this.getPerEntryOverhead(); size += sizeof(key); size += sizeof(value); return size; } /** * In addition to initializing the statistics, create an evictor thread to periodically evict * the LRU entry. */ @Override public LRUStatistics initStats(Object region, StatisticsFactory sf) { setRegionName(region); final LRUStatistics stats = new HeapLRUStatistics(sf, getRegionName(), this); setStats(stats); return stats; } public StatisticsType getStatisticsType() { return statType; } public String getStatisticsName() { return "HeapLRUStatistics"; } public int getLimitStatId() { throw new UnsupportedOperationException("Limit not used with this LRU type"); } public int getCountStatId() { return statType.nameToId("entryBytes"); } public int getEvictionsStatId() { return statType.nameToId("lruEvictions"); } public int getDestroysStatId() { return statType.nameToId("lruDestroys"); } public int getDestroysLimitStatId() { return statType.nameToId("lruDestroysLimit"); } public int getEvaluationsStatId() { return statType.nameToId("lruEvaluations"); } public int getGreedyReturnsStatId() { return statType.nameToId("lruGreedyReturns"); } /** * Okay, deep breath. Instead of basing the LRU calculation on the number of entries in the * region or on their "size" (which turned out to be incorrectly estimated in the general * case), we use the amount of memory currently in use. If the amount of memory current in use * {@linkplain Runtime#maxMemory max memory} - {@linkplain Runtime#freeMemory free memory} is * greater than the overflow threshold, then we evict the LRU entry. */ public boolean mustEvict(LRUStatistics stats, Region region, int delta) { final GemFireCacheImpl cache; if (region != null) { cache = (GemFireCacheImpl) region.getRegionService(); } else { cache = GemFireCacheImpl.getInstance(); } InternalResourceManager resourceManager = cache.getResourceManager(); if (region == null) { return resourceManager.getHeapMonitor().getState().isEviction(); } final boolean monitorStateIsEviction; if (!((AbstractRegion) region).getOffHeap()) { monitorStateIsEviction = resourceManager.getHeapMonitor().getState().isEviction(); } else { monitorStateIsEviction = resourceManager.getOffHeapMonitor().getState().isEviction(); } if (region instanceof BucketRegion) { return monitorStateIsEviction && ((BucketRegion) region).getSizeForEviction() > 0; } return monitorStateIsEviction && ((LocalRegion) region).getRegionMap().sizeInVM() > 0; } }; } // ////////////////////// Inner Classes //////////////////////// private ObjectSizer sizer; /** * Return the size of an object as stored in GemFire... Typically this is the serialized size in * bytes.. This implementation is slow.... Need to add Sizer interface and call it for customer * objects. */ protected int sizeof(Object o) throws IllegalArgumentException { return MemLRUCapacityController.basicSizeof(o, this.sizer); } public int getPerEntryOverhead() { return perEntryOverhead; } public void setEntryOverHead(int entryOverHead) { this.perEntryOverhead = entryOverHead; } }