是什么让热部署成为“难题”?

2022-09-01 00:06:05

在工作中,我们遇到了“PermGen 内存不足”异常的问题,团队负责人认为这是 JVM 中的一个错误 - 与代码的热部署有关。在没有解释许多细节的情况下,他指出,热部署是一个“难题”,甚至连.NET都还没有做到。

我发现很多文章从鸟瞰图中解释了热部署,但总是缺乏技术细节。任何人都可以给我一个技术解释,并解释为什么热部署是“一个难题”?


答案 1

加载类时,有关该类的各种静态数据存储在 PermGen 中。只要存在对此类实例的实时引用,就无法对类实例进行垃圾回收。

我认为,部分问题与 GC 是否应该从 perm gen 中删除旧的 Class 实例有关。通常,每次热部署时,都会将新的类实例添加到 PermGen 内存池中,而现在未使用的旧类实例通常不会被删除。默认情况下,Sun JVM 不会在 PermGen 中运行垃圾回收,但这可以通过可选的“java”命令参数启用。

因此,如果热部署足够多的次数,最终将耗尽 PermGen 空间。

如果 Web 应用在取消部署时未完全关闭(例如,如果它使线程保持运行状态),则该 Web 应用使用的所有类实例都将固定在 PermGen 空间中。您重新部署,现在已将所有这些类实例的另一个完整副本加载到 PermGen 中。取消部署后,线程将继续运行,在 PermGen 中固定另一组类实例。您重新部署并加载了整个网络副本集...最终你的烫发发电机填满了。

您有时可以通过以下方式解决此问题:

  • 向最近的 Sun JVM 提供命令参数,以在 PermGen 和类中启用 GC。那是:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
  • 使用不同的 JVM,该 JVM 不使用固定大小的 PermGen,或者在加载的类上执行 GC

但是,仅当 Web 应用完全且干净地关闭时,这会有所帮助,并且不会留下对该 Web 应用的类加载器加载的任何类的任何类的任何类实例的实时引用。

即使这样也不一定能解决问题,因为类加载器泄漏。(在某些情况下,以及过多的滞留字符串。

查看以下链接以获取更多信息(两个粗体链接有很好的图表来说明问题的一部分)。


答案 2

一般而言,问题在于 Java 的安全模型,它实际上试图阻止再次加载已加载的类。

当然,Java从一开始就支持动态类加载,难点的就是类重装。

一个正在运行的java应用程序被注入一个带有恶意代码的新类被认为是有害的(并且有充分的理由)。例如,来自互联网的java.lang.String破解实现,它不是创建字符串,而是删除一些随机文件hile,调用方法long()。

因此,他们构思Java的方式(我推测.NET CLR因此,因为它在JVM中受到高度“启发”)是为了防止已经加载的类再次加载相同的VM。

他们提供了一种机制来覆盖这个“功能”。类装入器,但类装入器的规则是,在尝试装入新类之前,它们应该向“父”类装入器请求权限,如果父类已经装入了类,则忽略新类。

例如,我使用了从LDAP或RDBMS加载类的类装入器。

当应用服务器成为Java EE的主流时,热部署在Java世界中成为必需品(并且也产生了对像spring这样的微型容器的需求,以避免这种负担)。

在每次编译后重新启动整个应用程序服务器会让任何人发疯。

因此,应用服务器提供程序,提供此“自定义”类装入器以帮助热部署,并使用配置文件,该行为应在生产中设置时禁用。但权衡是您必须在开发中使用大量内存。因此,执行此操作的好方法是每 3 - 4 次部署重新启动一次。

对于从一开始就设计用于加载其类的其他语言,则不会发生这种情况。

例如,在 Ruby 中,您甚至可以将方法添加到正在运行的类中,在运行时重写方法,甚至将单个方法添加到唯一的特定对象中。

这些环境中的权衡当然是内存和速度。

我希望这有帮助。

编辑

我前段时间发现这个产品承诺重新加载尽可能简单。当我第一次写这个答案时,我不记得链接,我确实记得。

它是来自ZeroTurnaround的JavaRebel


推荐