blob: 2eb9f56f4c2c607c606570c55d780ed28eab2494 [file] [log] [blame]
package org.apache.blur.utils;
/**
* 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.
*/
import static org.apache.blur.metrics.MetricsConstants.*;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import org.apache.blur.log.Log;
import org.apache.blur.log.LogFactory;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.MetricName;
public class GCWatcherJdk6 extends TimerTask {
private static final Log LOG = LogFactory.getLog(GCWatcherJdk6.class);
private static final String GET_LAST_GC_INFO = "getLastGcInfo";
private static final String CONCURRENT_MARK_SWEEP = "ConcurrentMarkSweep";
private static final String CMS_OLD_GEN = "CMS Old Gen";
private final Timer _timer;
private final long _period = TimeUnit.MILLISECONDS.toMillis(25);
private GarbageCollectorMXBean _bean;
private final double _ratio;
private Method _method;
private GcInfo _gcInfo;
private long _lastIndex;
private final List<GCAction> _actions = new ArrayList<GCAction>();
private final MemoryMXBean _memoryMXBean;
private static GCWatcherJdk6 _instance;
private final com.yammer.metrics.core.Timer _gcTimes;
private GCWatcherJdk6(double ratio) {
_memoryMXBean = ManagementFactory.getMemoryMXBean();
List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean bean : garbageCollectorMXBeans) {
String name = bean.getName();
if (name.equals(CONCURRENT_MARK_SWEEP)) {
_bean = bean;
try {
_method = _bean.getClass().getDeclaredMethod(GET_LAST_GC_INFO, new Class[] {});
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
_method.setAccessible(true);
}
}
_ratio = ratio;
if (_bean != null) {
_timer = new Timer("gc-watch", true);
_timer.schedule(this, _period, _period);
LOG.info("GCWatcherJdk6 was setup.");
} else {
_timer = null;
LOG.warn("GCWatcherJdk6 was NOT setup.");
}
MetricName gcTimesName = new MetricName(ORG_APACHE_BLUR, JVM, GC_TIMES);
_gcTimes = Metrics.newTimer(gcTimesName, TimeUnit.MILLISECONDS, TimeUnit.SECONDS);
}
public static void registerAction(GCAction action) {
GCWatcherJdk6 instance = instance();
if (instance != null) {
synchronized (instance._actions) {
instance._actions.add(action);
}
}
}
public static synchronized void shutdown() {
GCWatcherJdk6 instance = instance();
if (instance != null) {
if (instance._timer != null) {
instance._timer.purge();
instance._timer.cancel();
}
}
}
@Override
public void run() {
try {
Object gcInfo = getGcInfo(_bean);
if (gcInfo != null) {
if (_gcInfo == null) {
_gcInfo = new GcInfo(gcInfo);
} else {
_gcInfo.setGcInfo(gcInfo);
}
if (_lastIndex == _gcInfo.getIndex()) {
return;
}
long startTime = _gcInfo.getStartTime();
long endTime = _gcInfo.getEndTime();
Map<String, MemoryUsage> usageBeforeGc = _gcInfo.getUsageBeforeGc();
Map<String, MemoryUsage> usageAfterGc = _gcInfo.getUsageAfterGc();
MemoryUsage before = usageBeforeGc.get(CMS_OLD_GEN);
long usedBefore = before.getUsed();
MemoryUsage after = usageAfterGc.get(CMS_OLD_GEN);
long usedAfter = after.getUsed();
long totalTime = endTime - startTime;
_gcTimes.update(totalTime, TimeUnit.MILLISECONDS);
LOG.info("totalTime spent in GC [{0} ms] collected [{1} bytes]", totalTime, (usedBefore - usedAfter));
MemoryUsage heapMemoryUsage = _memoryMXBean.getHeapMemoryUsage();
long max = heapMemoryUsage.getMax();
long used = heapMemoryUsage.getUsed();
long upperLimit = (long) (max * _ratio);
if (used > upperLimit) {
LOG.error("----- WARNING !!!! - Heap used [{0}] over limit of [{1}], taking action to avoid an OOM error.", used,
upperLimit);
synchronized (_actions) {
for (GCAction action : _actions) {
try {
action.takeAction();
} catch (Exception e) {
LOG.error("Unknown error while trying to take action against an OOM [{0}]", e, action);
}
}
}
}
_lastIndex = _gcInfo.getIndex();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
private Object getGcInfo(GarbageCollectorMXBean bean) throws Exception {
return _method.invoke(bean, new Object[] {});
}
static class GcInfo {
private Object _gcInfo;
private final Field _startTimeField;
private final Field _endTimeField;
private final Field _usageAfterGcField;
private final Field _usageBeforeGcField;
private final Field _indexField;
GcInfo(Object o) throws SecurityException, NoSuchFieldException {
Class<? extends Object> clazz = o.getClass();
_startTimeField = setup(clazz.getDeclaredField("startTime"));
_endTimeField = setup(clazz.getDeclaredField("endTime"));
_usageAfterGcField = setup(clazz.getDeclaredField("usageAfterGc"));
_usageBeforeGcField = setup(clazz.getDeclaredField("usageBeforeGc"));
_indexField = setup(clazz.getDeclaredField("index"));
setGcInfo(o);
}
void setGcInfo(Object o) {
_gcInfo = o;
}
private Field setup(Field field) {
field.setAccessible(true);
return field;
}
long getStartTime() {
try {
return _startTimeField.getLong(_gcInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
long getEndTime() {
try {
return _endTimeField.getLong(_gcInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
long getIndex() {
try {
return _indexField.getLong(_gcInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
Map<String, MemoryUsage> getUsageAfterGc() {
try {
return (Map<String, MemoryUsage>) _usageAfterGcField.get(_gcInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
Map<String, MemoryUsage> getUsageBeforeGc() {
try {
return (Map<String, MemoryUsage>) _usageBeforeGcField.get(_gcInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public synchronized static void init(double ratio) {
if (_instance == null) {
try {
_instance = new GCWatcherJdk6(ratio);
} catch (Exception e) {
LOG.error("GCWatcher had error initializing", e);
}
} else {
LOG.warn("GCWatcher has already been initialized");
}
}
private static GCWatcherJdk6 instance() {
return _instance;
}
}