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; } }