blob: 4b0a33bec4adab1d2bf136baa30e6f96d2f86ba5 [file] [log] [blame]
/*
* 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.control;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.geode.DataSerializer;
import org.apache.geode.annotations.internal.MutableForTesting;
import org.apache.geode.cache.LowMemoryException;
import org.apache.geode.cache.control.ResourceManager;
import org.apache.geode.distributed.internal.DistributionConfig;
/**
* Stores eviction and critical thresholds for memory as well as the logic for determining how
* memory transitions between states.
*
* @since Geode 1.0
*/
public class MemoryThresholds {
public enum MemoryState {
DISABLED, // Both eviction and critical disabled
EVICTION_DISABLED, // Eviction disabled, critical enabled, critical threshold not exceeded
EVICTION_DISABLED_CRITICAL, // Eviction disabled, critical enabled, critical threshold exceeded
CRITICAL_DISABLED, // Critical disabled, eviction enabled, eviction threshold not exceeded
EVICTION_CRITICAL_DISABLED, // Critical disabled, eviction enabled, eviction threshold exceeded
NORMAL, // Both eviction and critical enabled, neither threshold exceeded
EVICTION, // Both eviction and critical enabled, eviction threshold exceeded
CRITICAL, // Both eviction and critical enabled, critical threshold exceeded
EVICTION_CRITICAL; // Both eviction and critical enabled, both thresholds exceeded
public static MemoryState fromData(DataInput in) throws IOException {
return MemoryState.values()[in.readInt()];
}
public void toData(DataOutput out) throws IOException {
DataSerializer.writeInteger(this.ordinal(), out);
}
public boolean isEvictionDisabled() {
return (this == DISABLED || this == EVICTION_DISABLED_CRITICAL || this == EVICTION_DISABLED);
}
public boolean isCriticalDisabled() {
return (this == DISABLED || this == EVICTION_CRITICAL_DISABLED || this == CRITICAL_DISABLED);
}
public boolean isNormal() {
return (this == NORMAL || this == EVICTION_DISABLED || this == CRITICAL_DISABLED);
}
public boolean isEviction() {
return (this == EVICTION || this == EVICTION_CRITICAL_DISABLED || this == EVICTION_CRITICAL);
}
public boolean isCritical() {
return (this == CRITICAL || this == EVICTION_DISABLED_CRITICAL || this == EVICTION_CRITICAL);
}
}
/**
* When this property is set to true, a {@link LowMemoryException} is not thrown, even when usage
* crosses the critical threshold.
*/
@MutableForTesting
private static boolean DISABLE_LOW_MEM_EXCEPTION =
Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "disableLowMemoryException");
/**
* The default percent of memory at which the VM is considered in a critical state.
*/
public static final float DEFAULT_CRITICAL_PERCENTAGE =
ResourceManager.DEFAULT_CRITICAL_PERCENTAGE;
/**
* The default percent of memory at which the VM should begin evicting data. Note that if a LRU is
* created and the eviction percentage has not been set then it will default to <code>80.0</code>
* unless the critical percentage has been set in which case it will default to a value
* <code>5.0</code> less than the critical percentage.
*/
public static final float DEFAULT_EVICTION_PERCENTAGE =
ResourceManager.DEFAULT_EVICTION_PERCENTAGE;
/**
* Memory usage must fall below THRESHOLD-THRESHOLD_THICKNESS before we deliver a down event
*/
private static final double THRESHOLD_THICKNESS = Double.parseDouble(
System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "thresholdThickness", "2.00"));
/**
* Memory usage must fall below THRESHOLD-THRESHOLD_THICKNESS_EVICT before we deliver an eviction
* down event
*/
private static final double THRESHOLD_THICKNESS_EVICT = Double.parseDouble(
System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "eviction-thresholdThickness",
Double.toString(THRESHOLD_THICKNESS)));
private final long maxMemoryBytes;
// Percent of available memory at which point a critical state is entered
private final float criticalThreshold;
// Number of bytes used at which point memory will enter the critical state
private final long criticalThresholdBytes;
// Percent of available memory at which point an eviction state is entered
private final float evictionThreshold;
// Number of bytes used at which point memory will enter the eviction state
private final long evictionThresholdBytes;
// Number of bytes used below which memory will leave the critical state
private final long criticalThresholdClearBytes;
// Number of bytes used below which memory will leave the eviction state
private final long evictionThresholdClearBytes;
/*
* Number of eviction or critical state changes that have to occur before the event is delivered.
* The default is 0 so we will change states immediately by default.
*/
@MutableForTesting
private static int memoryStateChangeTolerance =
Integer.getInteger(DistributionConfig.GEMFIRE_PREFIX + "memoryEventTolerance", 0);
// Only change state when these counters exceed {@link
// HeapMemoryMonitor#memoryStateChangeTolerance}
private transient int toleranceCounter;
MemoryThresholds(long maxMemoryBytes) {
this(maxMemoryBytes, DEFAULT_CRITICAL_PERCENTAGE, DEFAULT_EVICTION_PERCENTAGE);
}
/**
* Public for testing.
*/
public MemoryThresholds(long maxMemoryBytes, float criticalThreshold, float evictionThreshold) {
if (criticalThreshold > 100.0f || criticalThreshold < 0.0f) {
throw new IllegalArgumentException(
"Critical percentage must be greater than 0.0 and less than or equal to 100.0.");
}
if (evictionThreshold > 100.0f || evictionThreshold < 0.0f) {
throw new IllegalArgumentException(
"Eviction percentage must be greater than 0.0 and less than or equal to 100.0.");
}
if (evictionThreshold != 0 && criticalThreshold != 0
&& evictionThreshold >= criticalThreshold) {
throw new IllegalArgumentException(
"Critical percentage must be greater than the eviction percentage.");
}
this.maxMemoryBytes = maxMemoryBytes;
this.criticalThreshold = criticalThreshold;
this.criticalThresholdBytes = (long) (criticalThreshold * 0.01 * maxMemoryBytes);
this.criticalThresholdClearBytes =
(long) (this.criticalThresholdBytes - (0.01 * THRESHOLD_THICKNESS * this.maxMemoryBytes));
this.evictionThreshold = evictionThreshold;
this.evictionThresholdBytes = (long) (evictionThreshold * 0.01 * maxMemoryBytes);
this.evictionThresholdClearBytes = (long) (this.evictionThresholdBytes
- (0.01 * THRESHOLD_THICKNESS_EVICT * this.maxMemoryBytes));
}
public static boolean isLowMemoryExceptionDisabled() {
return DISABLE_LOW_MEM_EXCEPTION;
}
static void setLowMemoryExceptionDisabled(boolean toDisableLowMemoryException) {
DISABLE_LOW_MEM_EXCEPTION = toDisableLowMemoryException;
}
public MemoryState computeNextState(final MemoryState oldState, final long bytesUsed) {
assert oldState != null;
assert bytesUsed >= 0;
// Are both eviction and critical thresholds enabled?
if (this.evictionThreshold != 0 && this.criticalThreshold != 0) {
if (bytesUsed < this.evictionThresholdClearBytes
|| (!oldState.isEviction() && bytesUsed < this.evictionThresholdBytes)) {
toleranceCounter = 0;
return MemoryState.NORMAL;
}
if (bytesUsed < this.criticalThresholdClearBytes
|| (!oldState.isCritical() && bytesUsed < this.criticalThresholdBytes)) {
return checkToleranceAndGetNextState(MemoryState.EVICTION, oldState);
}
return checkToleranceAndGetNextState(MemoryState.EVICTION_CRITICAL, oldState);
}
// Are both eviction and critical thresholds disabled?
if (this.evictionThreshold == 0 && this.criticalThreshold == 0) {
return MemoryState.DISABLED;
}
// Is just critical threshold enabled?
if (this.evictionThreshold == 0) {
if (bytesUsed < this.criticalThresholdClearBytes
|| (!oldState.isCritical() && bytesUsed < this.criticalThresholdBytes)) {
toleranceCounter = 0;
return MemoryState.EVICTION_DISABLED;
}
return checkToleranceAndGetNextState(MemoryState.EVICTION_DISABLED_CRITICAL, oldState);
}
// Just the eviction threshold is enabled
if (bytesUsed < this.evictionThresholdClearBytes
|| (!oldState.isEviction() && bytesUsed < this.evictionThresholdBytes)) {
toleranceCounter = 0;
return MemoryState.CRITICAL_DISABLED;
}
return checkToleranceAndGetNextState(MemoryState.EVICTION_CRITICAL_DISABLED, oldState);
}
@Override
public String toString() {
return new StringBuilder().append("MemoryThresholds@[").append(System.identityHashCode(this))
.append(" maxMemoryBytes:" + this.maxMemoryBytes)
.append(", criticalThreshold:" + this.criticalThreshold)
.append(", criticalThresholdBytes:" + this.criticalThresholdBytes)
.append(", criticalThresholdClearBytes:" + this.criticalThresholdClearBytes)
.append(", evictionThreshold:" + this.evictionThreshold)
.append(", evictionThresholdBytes:" + this.evictionThresholdBytes)
.append(", evictionThresholdClearBytes:" + this.evictionThresholdClearBytes).append("]")
.toString();
}
public long getMaxMemoryBytes() {
return this.maxMemoryBytes;
}
public float getCriticalThreshold() {
return this.criticalThreshold;
}
public long getCriticalThresholdBytes() {
return this.criticalThresholdBytes;
}
public long getCriticalThresholdClearBytes() {
return this.criticalThresholdClearBytes;
}
public boolean isCriticalThresholdEnabled() {
return this.criticalThreshold > 0.0f;
}
public float getEvictionThreshold() {
return this.evictionThreshold;
}
public long getEvictionThresholdBytes() {
return this.evictionThresholdBytes;
}
public long getEvictionThresholdClearBytes() {
return this.evictionThresholdClearBytes;
}
public boolean isEvictionThresholdEnabled() {
return this.evictionThreshold > 0.0f;
}
void setMemoryStateChangeTolerance(int memoryStateChangeTolerance) {
MemoryThresholds.memoryStateChangeTolerance = memoryStateChangeTolerance;
}
int getMemoryStateChangeTolerance() {
return MemoryThresholds.memoryStateChangeTolerance;
}
/**
* Generate a Thresholds object from data available from the DataInput
*
* @param in DataInput from which to read the data
* @return a new instance of Thresholds
*/
public static MemoryThresholds fromData(DataInput in) throws IOException {
long maxMemoryBytes = in.readLong();
float criticalThreshold = in.readFloat();
float evictionThreshold = in.readFloat();
return new MemoryThresholds(maxMemoryBytes, criticalThreshold, evictionThreshold);
}
/**
* Write the state of this to the DataOutput
*
* @param out DataOutput on which to write internal state
*/
public void toData(DataOutput out) throws IOException {
out.writeLong(this.maxMemoryBytes);
out.writeFloat(this.criticalThreshold);
out.writeFloat(this.evictionThreshold);
}
/**
* To avoid memory spikes in JVMs susceptible to bad heap memory
* reads/outliers, we only deliver events if we receive more than
* memoryStateChangeTolerance of the same state change.
*
* @return New state if above tolerance, old state if below
*/
private MemoryState checkToleranceAndGetNextState(MemoryState newState, MemoryState oldState) {
return memoryStateChangeTolerance > 0
&& toleranceCounter++ < memoryStateChangeTolerance ? oldState : newState;
}
}