在工作中,我们遇到了“PermGen out of memory”异常的问题,团队领导决定它是JVM中的一个错误 - 与代码的热部署有关。在没有解释许多细节的情况下,他指出热部署是一个“难题”,即使.NET还没有这么做,也很难。
我从鸟瞰图中发现了很多解释热部署的文章,但总是缺乏技术细节。有人能指出我的技术解释,并解释为什么热部署是“一个难题”?
答案 0 :(得分:59)
加载类时,有关该类的各种静态数据存储在PermGen中。只要存在对此Class实例的实时引用,就不能对类实例进行垃圾回收。
我认为问题的一部分与GC是否应该从perm gen中删除旧的Class实例有关。通常,每次热部署时,都会将新的类实例添加到PermGen内存池中,而现在未使用的旧实例通常不会被删除。默认情况下,Sun JVM不会在PermGen中运行垃圾收集,但可以使用可选的“java”命令参数启用它。
因此,如果你进行足够的热部署,最终会耗尽你的PermGen空间。
如果你的网络应用程序在取消部署时没有关闭完全 - 例如,如果它让线程继续运行 - 那么该网络应用程序使用的所有类实例都将固定在PermGen空间。您重新部署,现在将所有这些Class实例的另一个完整副本加载到PermGen中。你取消部署并且Thread继续运行,在PermGen中固定另一组类实例。您重新部署并加载一整套复制副本......最终您的PermGen会填满。
您有时可以通过以下方式解决此问题:
-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
但如果您的网络应用程序完全干净地关闭,这将有助于仅,不会对该网络应用程序的类加载器加载的任何类的任何类实例保留实时引用。
由于类加载器泄漏,即使这也不一定能解决问题。 (在某些情况下,还有太多的实习字符串。)
查看以下链接了解更多信息(两个加粗的图表有很好的图表来说明部分问题)。
答案 1 :(得分:6)
一般来说问题是Java的安全模型,它实际上试图阻止再次加载已经加载的类。
当然Java从一开始就支持动态类加载,类重新加载很困难。
有人认为有害(并且有充分理由)正在运行的java应用程序注入了带有恶意代码的新类。例如,来自互联网的java.lang.String破解实现,而不是创建字符串,删除一些随机文件hile调用方法length()。
所以,他们构思Java的方式(因此我假设.NET CLR,因为它在JVM中受到高度启发)是为了防止已经加载的类再次加载相同的VM。
他们提供了一种机制来覆盖这个“功能”。类加载器,但同样是类加载器的规则,它们应该在尝试加载新类之前请求“父”类加载器的权限,如果父类已经加载了类,则忽略新类。
例如,我使用了从LDAP或RDBMS加载类的类加载器
当应用程序服务器成为Java EE的主流时,热部署成为Java世界的必需品(并且还需要像spring这样的微容器以避免这种负担)。
每次编译后重新启动整个应用服务器会让任何人疯狂。
所以应用服务器提供商,提供这个“自定义”类加载器来帮助热部署,并使用配置文件,这种行为,应该在生产中设置时被禁用。但权衡是你必须在开发中使用大量内存。因此,执行此操作的好方法是每3-4次重新启动一次。
对于从一开始设计加载其类的其他语言,就不会发生这种情况。
例如,在Ruby中,您甚至可以向正在运行的类添加方法,在运行时覆盖方法,甚至可以将单个方法添加到唯一的特定对象。
在这些环境中的权衡当然是记忆和速度。
我希望这会有所帮助。
修改强>
前段时间我发现这个产品承诺重新加载尽可能简单。当我第一次写这个答案时,我不记得这个链接,而且我确实这么做了。
答案 2 :(得分:3)
Sun JVM已经修复了PermGen空间,并最终消耗了它(是的,显然是由于类加载器相关代码中的错误)=> OOM。
如果您可以使用其他供应商的JVM(例如Weblogic),它会动态扩展PermGen空间,因此您永远不会获得与permgen相关的OOM。
答案 3 :(得分:0)
您使用的是哪个版本的Java?早期的Sun 1.4.2中存在漏洞,但它已经工作了很长时间。
顺便说一句,你如何将这个消息告诉你的团队领导?你是团队的主角吗?