[toc]
在MonitoringFilter调用destroy销毁的方法中,主要调用的方法:FilterContext#destroy
在过滤器上下文销毁的时候,调用了collector.stop();即停止计数器,
在停止计数器的时候, 会把计数器信息序列化到对应的.ser.gz文件中;
public void destroy() {if (monitoringDisabled || !instanceEnabled) {return;}final long start = System.currentTimeMillis();try {if (filterContext != null) {//过滤器上下文销毁 ,参见下文代码0filterContext.destroy();}} finally {final String contextPath = Parameters.getContextPath(filterConfig.getServletContext());CONTEXT_PATHS.remove(contextPath);// 把一些变量置为空 代码略}final long duration = System.currentTimeMillis() - start;LOG.debug("JavaMelody filter destroy done in " + duration + " ms");
void destroy() {try {try {if (collector != null) {// 写入相关信息到监控目录下的last_shutdown.html文件里new MonitoringController(collector, null).writeHtmlToLastShutdownFile();}} finally {//停止并注销jdbc wrapperJdbcWrapper.SINGLETON.stop();//销毁jdbcDriverderegisterJdbcDriver();// 移除loggingderegisterLogs();// 销毁定时器if (JobInformations.QUARTZ_AVAILABLE) {JobGlobalListener.destroyJobGlobalListener();}//注销CounterRequestMXBean bean。unregisterJmxExpose();}} finally {MonitoringInitialContextFactory.stop();// 定时器清除if (timer != null) {timer.cancel();}// 应该是停止采集cpu的一些东西if (samplingProfiler != null) {samplingProfiler.clear();}// 停止计数器, 但是在停止之前会把计数器写到文件 参见下文代码if (collector != null) {collector.stop();}// 调用的是JRobin.stop(); Robin注释:RRD存储和统计图表.....Collector.stopJRobin();//JVM的一些监控统计Collector.detachVirtualMachine();}}
public void stop() {try {try {for (final Counter counter : counters) {// 写入到这样的一个文件里://new File(storageDirectory, counter.getStorageName() + ".ser.gz");//写入的时候用的时候 ObjectOutputStream 也即直接把对象序列化到文件中counter.writeToFile();}} finally {storageLock.release();}} catch (final IOException e) {// persistance échouée, tant pisLOG.warn("exception while writing counters data to files", e);} finally {try {for (final Counter counter : counters) {counter.clear();}} finally {if (metricsPublishers != null) {for (final MetricsPublisher metricsPublisher : metricsPublishers) {metricsPublisher.stop();}}}stopped = true;// 一串看不懂的注释...................// ici on ne fait pas de nettoyage de la liste counters car cette méthode// est appelée sur la webapp monitorée quand il y a un serveur de collecte// et que cette liste est envoyée au serveur de collecte,// et on ne fait pas de nettoyage des maps qui servent dans le cas// où le monitoring de la webapp monitorée est appelée par un navigateur// directement même si il y a par ailleurs un serveur de collecte// (dans ce dernier cas les données sont bien sûr partielles)}}
代码太长,只粗略的浏览部分代码链
- 在MonitoringFilter#init中初始化过滤器上下文FilterContext,非常重要的一段代码
- FilterContext构造代码中,几个重要的逻辑:
- initCounters(); 初始化各种计数器
- 构造Collector对象,从文件中读取各个计数器的信息
- CollectTimerTask 新建收集器的定时任务, 这个定时器 为把收集到的信息写到文件★★★★★★★
public void init(FilterConfig config) throws ServletException {final long start = System.currentTimeMillis(); // NOPMDfinal String contextPath = Parameters.getContextPath(config.getServletContext());if (!instanceEnabled) {if (!CONTEXT_PATHS.contains(contextPath)) {instanceEnabled = true;} else {return;}}CONTEXT_PATHS.add(contextPath);this.filterConfig = config;this.servletApi2 = config.getServletContext().getMajorVersion() < 3;Parameters.initialize(config);monitoringDisabled = Parameter.DISABLED.getValueAsBoolean();if (monitoringDisabled) {return;}LOG.debug("JavaMelody filter init started");// 初始化过滤器上下文,非常重要的一段代码 ★★★★★ 见下文代码this.filterContext = new FilterContext(getApplicationType());this.httpAuth = new HttpAuth();config.getServletContext().setAttribute(ReportServlet.FILTER_CONTEXT_KEY, filterContext);final Collector collector = filterContext.getCollector();this.httpCounter = collector.getCounterByName(Counter.HTTP_COUNTER_NAME);this.errorCounter = collector.getCounterByName(Counter.ERROR_COUNTER_NAME);logEnabled = Parameter.LOG.getValueAsBoolean();rumEnabled = Parameter.RUM_ENABLED.getValueAsBoolean();if (Parameter.URL_EXCLUDE_PATTERN.getValue() != null) {urlExcludePattern = Pattern.compile(Parameter.URL_EXCLUDE_PATTERN.getValue());}}
FilterContext(final String applicationType) {super();assert applicationType != null;this.applicationType = applicationType;boolean initOk = false;// 新建守护进程的定时器this.timer = new Timer("javamelody"+ Parameters.getContextPath(Parameters.getServletContext()).replace('/', ' '),true);try {//打印一些系统信息和参数logSystemInformationsAndParameters();//初始化log相关, 判断使用的是哪种log,然后注册initLogs();//初始化MonitoringInitialContextFactory, 就是一个System.setProperty动作if (Parameter.CONTEXT_FACTORY_ENABLED.getValueAsBoolean()) {MonitoringInitialContextFactory.init();}//大概就是包裹jdbc,使之能够监控到sqlJdbcWrapper.SINGLETON.initServletContext(Parameters.getServletContext());if (!Parameters.isNoDatabase()) {JdbcWrapper.SINGLETON.rebindDataSources();} else {JdbcWrapper.SINGLETON.stop();}if (JobInformations.QUARTZ_AVAILABLE) {JobGlobalListener.initJobGlobalListener();}if (MOJARRA_AVAILABLE) {JsfActionHelper.initJsfActionListener();}if (JPA2_AVAILABLE) {JpaPersistence.initPersistenceProviderResolver();}// 初始化 samplingProfiler:通过定期采样线程的堆栈跟踪来检测CPU热点CPU。//里面是一个定时器每10s执行一次sampler.update();this.samplingProfiler = initSamplingProfiler();//初始化各种计数器 非常重要★★★★★ 参见下文代码final List<Counter> counters = initCounters();final String application = Parameters.getCurrentApplication();// ★★★★★★这个构造函数代码比较长, 其中有个重要的逻辑就是// 从文件中反序列化出每个计数器, 以及初始化 当前日期的计数器this.collector = new Collector(application, counters, this.samplingProfiler);// 新建收集器的定时任务, 这个定时器 为把收集到的信息写到文件★★★★★★★(代码太长 略)this.collectTimerTask = new CollectTimerTask(collector);//初始化 收集器,开启collectTimerTask// 使用自己的文件同步Timer 操作相关RrdinitCollect();if (Parameter.JMX_EXPOSE_ENABLED.getValueAsBoolean()) {initJmxExpose();}//检测melody版本? 定时器 10分钟后执行 每24小时执行一次UpdateChecker.init(timer, collector, applicationType);if (Parameters.getServletContext().getServerInfo().contains("Google App Engine")) {// https://issuetracker.google.com/issues/72216727final String fontConfig = System.getProperty("java.home")+ "/lib/fontconfig.Prodimage.properties";if (new File(fontConfig).exists()) {System.setProperty("sun.awt.fontconfig", fontConfig);}}initOk = true;} finally {if (!initOk) {// si exception dans initialisation, on annule la création du timer// (sinon tomcat ne serait pas content)timer.cancel();LOG.debug("JavaMelody init failed");}}}
private static List<Counter> initCounters() {final Counter sqlCounter = JdbcWrapper.SINGLETON.getSqlCounter();final Counter httpCounter = new Counter(Counter.HTTP_COUNTER_NAME, "dbweb.png", sqlCounter);final Counter errorCounter = new Counter(Counter.ERROR_COUNTER_NAME, "error.png");errorCounter.setMaxRequestsCount(250);final Counter jpaCounter = MonitoringProxy.getJpaCounter();final Counter ejbCounter = MonitoringProxy.getEjbCounter();final Counter springCounter = MonitoringProxy.getSpringCounter();final Counter guiceCounter = MonitoringProxy.getGuiceCounter();final Counter servicesCounter = MonitoringProxy.getServicesCounter();final Counter strutsCounter = MonitoringProxy.getStrutsCounter();final Counter jsfCounter = MonitoringProxy.getJsfCounter();final Counter logCounter = LoggingHandler.getLogCounter();final Counter jspCounter = JspWrapper.getJspCounter();final List<Counter> counters;if (JobInformations.QUARTZ_AVAILABLE) {final Counter jobCounter = JobGlobalListener.getJobCounter();counters = Arrays.asList(httpCounter, sqlCounter, jpaCounter, ejbCounter, springCounter,guiceCounter, servicesCounter, strutsCounter, jsfCounter, jspCounter,errorCounter, logCounter, jobCounter);} else {counters = Arrays.asList(httpCounter, sqlCounter, jpaCounter, ejbCounter, springCounter,guiceCounter, servicesCounter, strutsCounter, jsfCounter, jspCounter,errorCounter, logCounter);}//设置每个计数器对应的查询正则表达式setRequestTransformPatterns(counters);final String displayedCounters = Parameter.DISPLAYED_COUNTERS.getValue();if (displayedCounters == null) {// 默认情况下,将显示HTTP、SQL、Error和Log((和使用的)计数器。httpCounter.setDisplayed(true);sqlCounter.setDisplayed(!Parameters.isNoDatabase());errorCounter.setDisplayed(true);logCounter.setDisplayed(true);jpaCounter.setDisplayed(jpaCounter.isUsed());ejbCounter.setDisplayed(ejbCounter.isUsed());springCounter.setDisplayed(springCounter.isUsed());guiceCounter.setDisplayed(guiceCounter.isUsed());servicesCounter.setDisplayed(servicesCounter.isUsed());strutsCounter.setDisplayed(strutsCounter.isUsed());jsfCounter.setDisplayed(jsfCounter.isUsed());jspCounter.setDisplayed(jspCounter.isUsed());} else {setDisplayedCounters(counters, displayedCounters);}LOG.debug("counters initialized");return counters;}
去掉
public Collector(String application, List<Counter> counters,SamplingProfiler samplingProfiler) {super();assert application != null;assert counters != null;this.application = application;this.counters = Collections.unmodifiableList(new ArrayList<Counter>(counters));this.samplingProfiler = samplingProfiler;for (final Counter counter : counters) {for (final Counter otherCounter : counters) {assert counter == otherCounter || !counter.getName().equals(otherCounter.getName());}counter.setApplication(application);final Counter dayCounter = new PeriodCounterFactory(counter).createDayCounterAtDate(new Date());dayCountersByCounter.put(counter, dayCounter);}periodMillis = Parameters.getResolutionSeconds() * 1000;try {//反序列化计数器for (final Counter counter : counters) {counter.readFromFile();}// 反序列化当前日期的计数器for (final Counter counter : counters) {dayCountersByCounter.get(counter).readFromFile();}LOG.debug("counters data read from files in "+ Parameters.getStorageDirectory(application));} catch (final IOException e) {LOG.warn("exception while reading counters data from files in "+ Parameters.getStorageDirectory(application), e);}this.storageLock = new StorageLock(application);this.webappVersions = new WebappVersions(application);}
Counter#addRequestsAndErrors部分代码摘录
void addRequestsAndErrors(Counter newCounter) {assert getName().equals(newCounter.getName());for (final CounterRequest newRequest : newCounter.getRequests()) {if (newRequest.getHits() > 0) {final CounterRequest request = getCounterRequestInternal(newRequest.getName());synchronized (request) {request.addHits(newRequest);}}}int size = requests.size();// 默认最大10000final int maxRequests = getMaxRequestsCount();if (size > maxRequests) {//如果查询次数超过10000次(例如,SQL没有被复制)。//我们在这里尝试避免使记忆饱和()和硬盘饱和//在所有这些不同的申请中,删除不到10个HITS的申请。//((例如,对工厂内每年的聚合有用)//根据一个新的查询,因为这将由集合类完成for (final CounterRequest request : requests.values()) {if (request.getHits() < 10) {removeRequest(request.getName());size--;if (size <= maxRequests) {break;}}}}if (isErrorCounter()) {addErrors(newCounter.getErrors());}}
基于我对javamelody的了解非常有限,我觉得作者设计的大于一万条之后的清理逻辑应该是出于对内存的考虑吧, 因为,曾在测试环境,由于长时间未清理监控日志文件,在javamelody启动时候大概占用200M内存,若不限制的追加,还是有造成内存泄漏的可能的。
所以,我个人建议在一段时间之后可以尝试清理javamelody的监控日志文件(在关闭服务后清理)。