Problem
Monitoring podstawowych parametrów JVM z poziomu web aplikacji – przydatne zwłaszcza wtedy, gdy nasz serwer aplikacji/kontener serwletów nie pokazuje takich informacji w swojej webowej konsoli i nie mamy możliwości szybkiego podpięcia się VisualVM albo JConsole (brak aktywowanego jmxremote, firewalle, czy cokolwiek innego uniemożliwiającego połączenie), a podejrzewamy że coś niedobrego może dziać się z naszą aplikacją.
Rozwiązanie
Wrzucenie prostej stronki JSP, która nam takie parametry wyświetli.
Poniżej listing (po moich drobnych modyfikacjach) zapożyczony z http://www.freshblurbs.com/explaining-java-lang-outofmemoryerror-permgen-space
Skryptlet (tak wiem, śmierdzi) użyty na tej stronie pozwala wrzucać ją do aplikacji bez potrzeby restartu serwera/redeploy aplikacji.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ page import="java.util.*, java.lang.management.*" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JVM Memory Monitor</title>
<style type="text/css">
td {
text-align: right;
}
</style>
</head>
<body>
<jsp:scriptlet>
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
pageContext.setAttribute("memoryBean", memoryBean, PageContext.PAGE_SCOPE);
List poolBeans = ManagementFactory.getMemoryPoolMXBeans();
pageContext.setAttribute("poolBeans", poolBeans, PageContext.PAGE_SCOPE);
</jsp:scriptlet>
<h3>Total Memory</h3>
<table border="1" width="100%">
<tr>
<th>usage</th>
<th>init</th>
<th>used</th>
<th>committed</th>
<th>max</th>
</tr>
<tr>
<td style="text-align: left">Heap Memory Usage</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.heapMemoryUsage.init / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.heapMemoryUsage.used / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.heapMemoryUsage.committed / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.heapMemoryUsage.max / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
</tr>
<tr>
<td style="text-align: left">Non-heap Memory Usage</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.nonHeapMemoryUsage.init / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.nonHeapMemoryUsage.used / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.nonHeapMemoryUsage.committed / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${pageScope.memoryBean.nonHeapMemoryUsage.max / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
</tr>
</table>
<h3>Memory Pools</h3>
<table border="1" width="100%">
<tr>
<th>Name</th>
<th>Usage</th>
<th>Init</th>
<th>Used</th>
<th>Committed</th>
<th>Max</th>
</tr>
<c:forEach var="bean" items="${pageScope.poolBeans}">
<tr>
<td style="text-align: left"><c:out value="${bean.name}" /></td>
<td style="text-align: left">Memory Usage</td>
<td><fmt:formatNumber value="${bean.usage.init / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${bean.usage.used / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${bean.usage.committed / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${bean.usage.max / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
</tr>
<tr>
<td></td>
<td style="text-align: left">Peak Usage</td>
<td><fmt:formatNumber value="${bean.peakUsage.init / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${bean.peakUsage.used / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${bean.peakUsage.committed / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
<td><fmt:formatNumber value="${bean.peakUsage.max / (1024 * 1024)}" maxFractionDigits="1"/> MB</td>
</tr>
</c:forEach>
</table>
</body>
</html>
Efekt poniżej:
To rozwiązanie tak mi się spodobało, że z rozpędu napisałem sobie to samo w Richfaces. Strona odświeża się automatycznie co zadany przedział czasu (w ms). Wykorzystanie Ajax Push/Poll nie powala na pewno z punktu widzenia wydajności, jest natomiast rozwiązaniem banalnym do zaimplementowania:
<html xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich">
<head>
<title>JVM Memory Monitor</title>
</head>
<body>
<a4j:region>
<h:form>
<a4j:poll id="poll" interval="#{jvmPerformanceManager.interval}" enabled="#{jvmPerformanceManager.pollEnabled}" reRender="polledPanel, poll"/>
</h:form>
</a4j:region>
<h:form prependId="false">
<h:panelGrid id="polledPanel" columns="1">
<h3>Total Memory</h3>
<rich:dataTable value="2" style="text-align: right">
<f:facet name="header">
<rich:columnGroup>
<rich:column>
<h:outputText value="Name" />
</rich:column>
<rich:column>
<h:outputText value="Init" />
</rich:column>
<rich:column>
<h:outputText value="Used" />
</rich:column>
<rich:column>
<h:outputText value="Committed" />
</rich:column>
<rich:column>
<h:outputText value="Max" />
</rich:column>
</rich:columnGroup>
</f:facet>
<rich:column>
<h:outputText value="Heap Memory Usage" />
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.heapMemoryUsage.init / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.heapMemoryUsage.used / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.heapMemoryUsage.committed / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.heapMemoryUsage.max / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column breakBefore="true">
<h:outputText value="Non-heap Memory Usage" />
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.nonHeapMemoryUsage.init / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.nonHeapMemoryUsage.used / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.nonHeapMemoryUsage.committed / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{jvmPerformanceManager.memoryBean.nonHeapMemoryUsage.max / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<f:facet name="footer">
<rich:columnGroup>
<rich:column>Last actualization date</rich:column>
<rich:column colspan="4">
<h:outputText value="#{jvmPerformanceManager.lastActualization}">
<f:convertDateTime pattern="yyyy-MM-dd HH:mm:ss" timeZone="#{jvmPerformanceManager.timeZone}"/>
</h:outputText>
</rich:column>
</rich:columnGroup>
</f:facet>
</rich:dataTable>
<h3>Memory Pools</h3>
<rich:dataTable var="poolBean" value="#{jvmPerformanceManager.poolList}"
style="text-align: right">
<f:facet name="header">
<rich:columnGroup>
<rich:column>
<h:outputText value="Name" />
</rich:column>
<rich:column>
<h:outputText value="Usage" />
</rich:column>
<rich:column>
<h:outputText value="Init" />
</rich:column>
<rich:column>
<h:outputText value="Used" />
</rich:column>
<rich:column>
<h:outputText value="Committed" />
</rich:column>
<rich:column>
<h:outputText value="Max" />
</rich:column>
</rich:columnGroup>
</f:facet>
<rich:column rowspan="2">
<h:outputText value="#{poolBean.name}" />
</rich:column>
<rich:column>
<h:outputText value="Memory" />
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.usage.init / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.usage.used / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.usage.committed / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.usage.max / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column breakBefore="true">
<h:outputText value="Peak" />
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.peakUsage.init / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.peakUsage.used / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText
value="#{poolBean.peakUsage.committed / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
<rich:column>
<h:outputText value="#{poolBean.peakUsage.max / (1024 * 1024)}">
<f:convertNumber pattern="#,##0.0 MB" locale="US" />
</h:outputText>
</rich:column>
</rich:dataTable>
<h:panelGrid columns="2">
<h:column>
<a4j:commandButton id="controlBtn"
value="#{jvmPerformanceManager.pollEnabled?'Stop':'Start'}" reRender="polledPanel, poll">
<a4j:actionparam name="polling" value="#{!jvmPerformanceManager.pollEnabled}"
assignTo="#{jvmPerformanceManager.pollEnabled}" />
</a4j:commandButton>
</h:column>
<h:column>
<a4j:outputPanel style="display: inline" rendered="#{!jvmPerformanceManager.pollEnabled}">
<h:outputText value=" with " />
<h:inputText value="#{jvmPerformanceManager.interval}">
<f:validateLongRange minimum="500" />
</h:inputText>
<h:outputText value=" ms interval." />
</a4j:outputPanel>
</h:column>
</h:panelGrid>
</h:panelGrid>
</h:form>
</body>
</html>
package info.ludera.helper;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.event.ActionEvent;
@ManagedBean(name="jvmPerformanceManager")
@SessionScoped
public class JvmPerformanceManager implements Serializable {
private MemoryMXBean memoryBean;
private List<MemoryPoolMXBean> poolList;
private Boolean pollEnabled = true;
/**
* Interval in milliseconds. Default 10s.
*/
private Long interval = 10*1000l;
private String timeZone;
public JvmPerformanceManager() {
getActualMemoryInfo(null);
timeZone = readDefaultHostTimeZone();
}
private String readDefaultHostTimeZone() {
return TimeZone.getDefault().getID();
}
public void getActualMemoryInfo(ActionEvent actionEvent) {
memoryBean = ManagementFactory.getMemoryMXBean();
poolList = ManagementFactory.getMemoryPoolMXBeans();
}
public MemoryMXBean getMemoryBean() {
return memoryBean;
}
public List<MemoryPoolMXBean> getPoolList() {
return poolList;
}
public Boolean getPollEnabled() {
return pollEnabled;
}
public void setPollEnabled(Boolean pollEnabled) {
this.pollEnabled = pollEnabled;
}
public Long getInterval() {
return interval;
}
public void setInterval(Long interval) {
this.interval = interval;
}
public Date getLastActualization() {
return Calendar.getInstance().getTime();
}
public String getTimeZone() {
return timeZone;
}
}
Efekt:
Jestem obecnie, jak pewnie wielu, na etapie rozpoznawania nowości w Java EE 6. Treningowo, postanowiłem zatem napisać ten sam JVM Monitor z wykorzystaniem Asynchronious Processing Support z Servlets 3.0 – także wkrótce rozwinięcie tematu.





Jeśli ktoś woli ICEFaces, to w wersji 2 pojawił się świetny mechanizm Push (PushRenderer), który można użyć w moim tworze. Więcej na http://wiki.icefaces.org/display/ICE/ICEpush oraz Use Case: http://in.relation.to/Bloggers/AHitchhikersGuideToJavaEE6ApplicationSetupPartI
[...] 1. Clean code, clean logs 2. Screencasty Jacka Laskowskiego 3. What is "Done"? 4. Kreatywność użytkownika; nie istnieje/a może jest za duża? 5. Michał Orman 6 . Poziomy izolacji transakcji 7. Monitoring podstawowych parametrów JVM [...]