扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
WEB请求经常导致内存泄漏,在一个WEB请求中,对象会被限制存储在有限的几个区域。这些区域就是:
当实现一些JSP(JAVASERVER页面)时,在页面上声明的变量在页面结束的时候就被释放,这些变量仅仅在这个单独的页面存在时存在。WEB服务器会向应用程序服务器传送一系列参数和属性,也就是在SERVLET和JSP之间传输HttpServletRequest中的对象。你的动态页面依靠HttpServletRequest在不同的组件之间传输信息,但当请求完成或者socket结束的时候,SERVLET控制器会释放所有在HttpServletRequest 中的对象。这些对象仅在他们的请求的生命周期内存在。
HTTP是无状态的,这意味着客户向服务器发送一个请求,服务器回应这个请求,这个传递就完成了,就是会话结束了。我们应该感激WEB页面帮我们做的日志,这样我们就能向购物车放置东西,并去检查它,服务器能够定义一个跨越多请求的扩展对话。属性和参数被放在各自用户的HttpSession对象中,并通过它让程序的SERVLET和JSP交流。利用这种办法,页面存储你的信息并把他们添加到HttpSession中,因此你可以用购物车购买东西,并检查商品和使用信用卡付帐。作为一个无状态的协议,它总是客户端发起连接请求,服务器需要知道一个会话存在多长时间,到时候就应该释放这个用户的数据。超过这个会话的最长时间就是会话超时,他们在程序服务器中设置。除非明确的要求释放对象或者这个会话失效,否则在会话超时之前会话中的对象会一直存在。
正如session是为每个用户管理对象一样,ServletContext为整个程序管理对象。ServletContext的有效范围是整个程序,因此你可以利用Servlet中的ServletContext或者JSP应用程序对象在所有的Servlet和JSP之间让在这个程序中的所有用户共享数据。ServletContext是最主要的存放程序配置信息和缓存程序数据的地方,例如JNDI的信息。
如果数据不是存储这个四个地方(页面范围,请求范围,会话范围,程序范围)那就可能存储在下面的对象中:
每个类的静态变量被JVM(JAVA虚拟机)所控制,他们存在与否和类是否已经被初始化无关。一个类的所有实例共用一个存储静态变量的地方,因此在任何一个实例中修改静态变量会影响这个类的其他实例。因此,如果一个程序在静态变量中存放了一个对象,如果这个变量生命周期没有到,那么这个对象就不会被JVM释放。这些静态对象是造成内存泄漏的主要原因。
最后,对象能够被放到内部数据类型或者长生命周期类中的成员变量中,例如SERVLET。当一个SERVLET被创建并且被装载到内存,它在内存中仅有一个实例,采用多线程去访问这个SERVLET实例。如果在INIT()方法中装载配置信息,将他存储于类变量中,那么当需要维护的时候就可以随时读出这些信息,这样所有的对象就用相同的配置。我常碰到的一个问题就是利用SERVLET类变量去存储象页面缓存这样的信息。在他们自己内部本身存贮这些缓存配置是个不错的选择,但存贮在SERVLET中是最糟糕的情况。如果你需要使用缓存,你最好使用第三方控制插件,例如 TANGOSOL的COHERENCE。
当在页面或者请求范围中利用变量存放对象的时候,在他们结束的时候这些对象会自动释放。同样,在SESSION中存放对象的时候,当程序明确说明此SESSION失效的或者会话执行超时的时候,这些对象才会自动被释放。
很多看起来象内存泄漏的情况都是上面的那些会话中的泄漏。一个造成泄漏的会话并不是泄漏了内存而是类似于泄漏,它消耗了内存,但最终这些内存都会被释放的。如果程序服务器发生内存溢出,判断是内存泄漏还是内存缺乏的最好的方法就是:停止所有向这个服务器所发的请求的对象,等待会话超时,看内存时候会被释放出来。这虽然不会一定能够达到你要的目的,但是这是最好的分段处理方法,当你装载测试器的时候,你应该先挂断你内容巨大的会话而不是先去寻找内存泄漏。
通常来说,如果你执行了一个很大的会话,你应该尽量去减少它所占用的内存空间,如果可以的话最好能重构程序,以减少session所占据的内存空间。下面2种方法可以降低大会话和内存的冲突:
一个巨大的堆会导致垃圾回收花费更多的时间,因此这不是一个好解决方法,但总比发生OutofMemoryError强。增加足够的堆空间以使它能够存储所有应该保存的有效值,也意味着你必须有足够的内存去存储所有访问你站点的用户的有效会话。如果商业规则允许的话最好能缩短会话超时的时间,以减少堆占用空间的冲突。
总结下,你应该依据合理性和重要性按下面的步骤依次去执行:
无论如何,不要让程序范围级的变量,静态变量,长生命周期的类存储对象,事实上,你需要在内存模拟器中去分析泄漏。
异常的持久空间
容易误解JVM为持久空间分配内存的目的。堆仅仅存储类的实例,但JVM在堆中创建类实例之前,它必须把字节流文件(.class文件)装载到程序内存中。它利用内存中的字节流在堆中创建类的实例。JVM利用程序的内存来装载字节流文件,这个内存空间称为持久空间。图6显示了持久空间和堆的关系:它存在于JVM程序中,并不是堆的一部分。
Figure 6. The relationship between the permanent space and the heap
通常,你可能想让你的持久空间足够大以便于它能够装载你程序所有的类,因为很明显,从文件系统中读取类文件比从内存中装载代价高很多。JVM提供了一个参数让你不的程序不卸载已经装载到持久空间中的类文件:
–noclassgc
这个参数选项告诉JVM不要跑到持久空间去执行垃圾收集释放其中已经装载的类文件。这个参数选项很聪明,但是会引起一个问题:当持久空间满了以后依然需要装载新文件的时候JVM会怎么处理呢?我观测到的资料说明:如果JVM检测到持久空间还需要内存,就会调用主垃圾收集程序。垃圾收集器清除堆,但它并不会对持久空间进行任何操作,因此它的努力是白费的。于是JVM就再重新检测持久空间,看它是否满,然后再次执行程序,一遍的一遍重复。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者