英文:
Memory Leak from LazyMOMProvider
问题
我的Java程序突然占用了太多内存,所以在其内存使用高峰时,我将其内容转储并在Eclipse Memory Analyzer中进行了分析。我发现LazyMOMProvider
通过endpointsWaitingForMOM
占用了90%的内存,达到了2.7GB。
现在我进行了一些分析,我在我的代码中从未使用过那个类,也许它被一些我使用的依赖项调用,而这些依赖项本身依赖于这个类,但我完全不明白发生了什么。
有没有人遇到过类似的问题,可以帮助我解决?
英文:
my java program is eating a bit too much memory suddenly, and so i dumped its contents while it was in a high usage state and analysed it in Eclipse Memory Analyzer and i get that LazyMOMProvider
through endpointsWaitingForMOM
is eating up 90% of its memory to an amount of 2.7Gb:
Now i did some analyzing and i DO NOT use that class ever in my code, maybe it's getting called by some dependency i use that has a dependency on this class itself, but do not understand at all what is happening here.
Anyone have had the same issue that can help me out?
答案1
得分: 0
这个类来自glassfish应用服务器,很难深入挖掘或预测,除非亲自动手处理服务器和源代码,但这几乎是不可能的,因为你自己也不知道导致了什么问题。
public enum LazyMOMProvider {
...
// 枚举类型的作用域
public static enum Scope {
...
}
// 作用域变化监听器接口
public static interface ScopeChangeListener {
void scopeChanged(Scope scope);
}
// 默认作用域变化监听器接口
public static interface DefaultScopeChangeListener extends ScopeChangeListener {
}
// Web服务端点作用域变化监听器接口
public static interface WSEndpointScopeChangeListener extends ScopeChangeListener {
}
...
// 初始化提供者的作用域
public void initMOMForScope(LazyMOMProvider.Scope scope) {
...
}
// 触发作用域变化事件
private void fireScopeChanged() {
...
}
// 注册监听器
public void registerListener(DefaultScopeChangeListener listener) {
...
}
// 判断提供者是否在默认作用域
private boolean isProviderInDefaultScope() {
...
}
// 获取当前作用域
public Scope getScope() {
return scope;
}
// 注册Web服务端点作用域变化监听器
public void registerEndpoint(WSEndpointScopeChangeListener wsEndpoint) {
...
}
// 从监听器列表中注销Web服务端点
public void unregisterEndpoint(WSEndpointScopeChangeListener wsEndpoint) {
...
}
}
源代码: LazyMOMProvider.java
快速的故障排除建议是在测试环境中尝试以下操作:
- 确保您正在使用Java 8,您可以升级到Java 11或17。
- 尝试将您的应用从glassfish中移出,并将其迁移到更好的服务器,如IBM WebSphere、Oracle Weblogic或Oracle应用服务器等,然后查看您的应用程序的性能。
- 在最坏的情况下,尝试升级您的glassfish服务器,然后尝试运行该应用程序,如果您无法控制升级Java或更换服务器。
可能需要一天的时间,但相信我,这会有所帮助。
英文:
This class comes from glassfish App server which is very hard to dig in or predict until getting my hands dirty to the server and source code which is impossible, since you yourself don't know what causes the issue.
public enum LazyMOMProvider {
INSTANCE;
/**
* Possible scopes (environments) in which the provider (and object associated with it) could be in.
* Default scope is STANDALONE - applied during initialization of classes. For now, only possible scope change for a
* object can be in this direction: STANDALONE -> GLASSFISH_NO_JMX -> GLASSFISH_JMX.
*/
public static enum Scope {
//** Default scope where lazy flag is not applied and all Gmbal API calls are processed immediately. */
STANDALONE,
/** In this scope almost all Gmbal API call are deferred until a JMX connection to a Glassfish server is created */
GLASSFISH_NO_JMX,
/** Same as STANDALONE. Gmbal API calls are processed immediately. */
GLASSFISH_JMX
}
/**
* Interface for all object that want to be notified about scope change, introducing required methods.
*/
public static interface ScopeChangeListener {
void scopeChanged(Scope scope);
}
/**
* Default interface for all object that want to be notified about scope change. This interface should not be
* implemented directly.
*/
public static interface DefaultScopeChangeListener extends ScopeChangeListener {
}
/**
* Interface used for distinguishing between a registration of a WSEndpointImpl rather than of other classes.
* Webservice Endpoints should get a notification about scope change sooner than the rest of the registered listeners
* (there is a possibility that other listeners are dependant on Webservice Endpoints).
*/
public static interface WSEndpointScopeChangeListener extends ScopeChangeListener {
}
private final Set<WSEndpointScopeChangeListener> endpointsWaitingForMOM = new HashSet();
private final Set<DefaultScopeChangeListener> listeners = new HashSet();
private volatile Scope scope = Scope.STANDALONE;
/**
* Initializes this provider with a given scope. If the given scope is different than the one this provider is
* currently in and the transition between scopes is valid then a event is fired to all registered listeners.
*
* @param scope a scope to initialize this provider with
*/
public void initMOMForScope(LazyMOMProvider.Scope scope) {
// cannot go backwards between scopes, for possible scope changes see #Scope
if ((this.scope == Scope.GLASSFISH_JMX)
|| (scope == Scope.STANDALONE && (this.scope == Scope.GLASSFISH_JMX || this.scope == Scope.GLASSFISH_NO_JMX))
|| this.scope == scope) {
return;
}
this.scope = scope;
fireScopeChanged();
}
/**
* Notifies the registered listeners about the scope change.
*/
private void fireScopeChanged() {
for (ScopeChangeListener wsEndpoint : endpointsWaitingForMOM) {
wsEndpoint.scopeChanged(this.scope);
}
for (ScopeChangeListener listener : listeners) {
listener.scopeChanged(this.scope);
}
}
/**
* Registers the given object as a listener.
*
* @param listener a listener to be registered
*/
public void registerListener(DefaultScopeChangeListener listener) {
listeners.add(listener);
if (!isProviderInDefaultScope()) {
listener.scopeChanged(this.scope);
}
}
/**
* Returns {@code true} if this provider is in the default scope.
*
* @return {@code true} if this provider is in the default scope,
* {@code false} otherwise
*/
private boolean isProviderInDefaultScope() {
return this.scope == Scope.STANDALONE;
}
public Scope getScope() {
return scope;
}
/**
* Registers a Webservice Endpoint as a listener.
* Webservice Endpoints should rather register through this method than through LazyMOMProvider#registerListener
* because generally they need to be notified sooner than arbitrary listener (i.e. object that is dependant on
* Webservice Endpoint)
*
* @param wsEndpoint a Webservice Endpoint to be registered
*/
public void registerEndpoint(WSEndpointScopeChangeListener wsEndpoint) {
endpointsWaitingForMOM.add(wsEndpoint);
if (!isProviderInDefaultScope()) {
wsEndpoint.scopeChanged(this.scope);
}
}
/**
* Unregisters a Webservice Endpoint from the list of listeners.
*
* @param wsEndpoint a Webservice Endpoint to be unregistered
*/
public void unregisterEndpoint(WSEndpointScopeChangeListener wsEndpoint) {
endpointsWaitingForMOM.remove(wsEndpoint);
}
}
Source Code: LazyMOMProvider.java
My quick troubleshooting advice is to try the following in testbed
- you must be using java 8 which you can upgrade to Java 11 or 17
- Try moving your app out from glassfish and migrate it to better server like IBM WebSphere or Oracle Weblogic or Oracle App server, etc and see your application's performance.
- In the worst case, Try upgrading your glassfish server and try running the app if you have no command over upgrading Java or server replacements.
It might take a day. but Trust me It helps.
答案2
得分: 0
看起来你的应用程序似乎在某些以endpointsWaitingForMOM结尾的端点上注册了一些东西,并且从不取消注册它们 —> 内存泄漏,所以你需要以某种方式调用那个unregisterEndpoint方法。
查看该类的Javadoc,其中对如何在独立应用程序中使用它进行了一些描述 - 也许这会为解决方案提供一些启发。
英文:
It looks like your application somehow registers some endpoints which ends in endpointsWaitingForMOM HashSet and never un-registers them --> memory leak, so you need somehow call that unregisterEndpoint method.
Have a look at the Javadoc of the class - there is some description on how to use it in standalone applications - maybe this shines some light on the solution.
答案3
得分: 0
尽管在我看来是不合逻辑的,但这种情况起因于我们的一个 Web 服务在半夜被重置。
显然,这个 Web 服务在凌晨1点到3点之间每30秒被重置一次,重置方法不起作用,因为它保持对象活跃,而它们没有被垃圾回收。
通过使用不同的停止 Web 服务的方法,对象被正确销毁,这种情况就消失了。
英文:
Even though illogic in my opinion, this situation stemmed from a WebService we had being reset in the middle of the night.
Apparently the WebService was being reset every 30 seconds between 1 and 3 AM, the reset method doesn't work as it should, because it keeps the objects alive and they are not GC'd.
By using a different method of stopping the WebService the objects are properly killed and this situation is gone.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论