一、引言
----
随着信息技术的快速发展,Java在企业级应用中的普及率越来越高。
由于其强大的跨平台能力和丰富的库资源,Java成为了许多企业和开发者的首选开发语言。
在搭建和运行Java服务器时,内存管理是一项至关重要的任务。
如果处理不当,可能会导致内存泄漏、性能瓶颈等问题。
本文将详细解析Java服务器内存管理,帮助读者了解如何避免这些问题并成功搭建高效的Java服务器。
二、Java服务器内存管理概述
----------
Java服务器的内存管理主要涉及两个重要方面:JVM的内存管理和应用层面的内存管理。
JVM(Java虚拟机)负责管理和分配物理内存资源给Java应用程序,包括堆内存、栈内存、方法区等。
应用层面的内存管理则涉及到对象生命周期管理、缓存管理等。
三、JVM的内存管理
--------
堆是JVM中最大的内存区域,用于存储大部分的对象实例。
废品回收器(GC)负责自动回收不再使用的对象,释放堆内存空间。
为了避免内存泄漏,开发者应避免创建长期持有的无用对象引用,并确保及时释放不再使用的资源。
选择合适的废品回收器和配置合适的堆大小也是关键。
栈用于存储基本数据类型和对象的引用。
每个线程在启动时都会创建一个栈。
对于栈内存管理,需要确保每个线程的栈空间使用合理,避免深度过深的递归调用导致栈溢出。
方法区用于存储已加载的类信息、常量、静态变量等。
合理的类加载策略和对常量池的合理使用对于避免内存泄漏和提高性能至关重要。
还需关注元空间的管理,避免类加载过多导致的元空间溢出问题。
四、应用层面的内存管理
-----------
了解Java对象的生命周期对于避免内存泄漏至关重要。
确保及时释放不再使用的对象引用,避免长期持有无用对象导致内存占用。
同时,合理使用WeakReference、SoftReference和PhantomReference等弱引用机制,有助于更好地管理对象的生命周期。
在Java服务器中,缓存是提高性能的重要手段。
不合理的缓存策略可能导致内存泄漏和性能问题。
开发者需要根据应用需求选择合适的缓存策略,并设置合理的缓存大小和失效时间。
还需要关注缓存中的对象是否及时被回收,避免内存泄漏。
在使用输入输出流、数据库连接等资源时,确保及时关闭和释放资源是非常重要的。
使用try-catch-finally或try-with-resources语句可以确保资源在使用后被正确关闭和释放。
使用连接池等技术也有助于管理和复用资源,避免资源浪费和内存泄漏。
五、如何搭建高效的Java服务器
------------
根据应用需求和负载情况选择合适的操作系统和硬件环境是搭建高效Java服务器的基础。
例如,对于高并发应用,可以选择支持多线程优化的操作系统和配置足够的内存和CPU资源。
还需要关注磁盘IO性能和网络性能等因素。
总之要确保硬件和软件环境能够满足应用的性能需求并保持良好的稳定性。
对于系统架构的优化还需要合理部署分布式缓存等中间件来提高系统性能降低单节点的压力;还可以通过负载均衡技术分配网络请求至不同服务器以进一步提高系统的可伸缩性和容错能力 。
分布式系统的应用也能使得服务器压力得到分散;服务器集群技术的部署则可以提高系统的响应速度 。
系统监控与报警机制的建立也是必不可少的 ;合理有效的日志记录和分析同样重要以便对系统性能进行调优 。
对代码的优化同样不可忽视编写高效且稳定的代码可以有效减少服务器的运行压力 。
定期更新维护服务器安全与系统漏洞也是极其重要的环节 ,它能保证服务器的安全稳定运行 。
建立合理的数据备份恢复机制能有效避免因数据丢失带来的损失风险 。
回滚计划和应急响应计划的建立能够减小系统出现问题时的风险影响 。
优秀的团队沟通与协作是保证高效部署服务器与优化的前提 ,能帮助团队更好地应对各种挑战 。
使用云计算技术还能进一步帮助开发者解决在开发和运维过程中的许多困难提供更高的灵活性可扩展性等特点 。
此外选择优秀的开发框架和语言也是提高效率的重要一环 Java本身的跨平台特性和丰富的库资源使其成为一个很好的选择 。
通过合理的架构设计和技术选型能够搭建出高效稳定的Java服务器为企业的业务发展提供强大的支撑 。
。
结论 搭建高效的Java服务器需要从多个方面进行考虑和执行包括JVM的内存管理和应用层面的内存管理以及系统架构部署优化等方面开发者需要不断学习和实践掌握最新的技术和工具以确保服务器的稳定性和性能以满足业务发展的需求同时还需要注重团队协作和沟通以便更好地应对各种挑战和问题 。
总之只有不断优化和提升才能为企业提供更加优质的服务和支持 。
JavaHeapSpace的意思
JavaHeapSpace是Java虚拟机中用来描述堆内存区域的一个术语。 在Java程序中,内存主要分为几个不同的区域,其中堆内存是用于动态分配对象实例的存储空间。 当Java应用程序创建对象时,这些对象会被存储在堆内存中。 而JavaHeapSpace就是指这些对象所占用的一部分内存区域。 当JVM检测到堆内存不足时,就会抛出OutOfMemoryError错误,此时需要调整堆内存的大小或者检查程序是否存在内存泄漏等问题。 下面将详细解释JavaHeapSpace的相关知识。
JavaHeapSpace的详细解释
一、Java内存结构概述
在Java应用程序运行时,JVM会将内存划分为几个不同的部分,包括堆内存、栈内存、方法区等。 其中,堆内存是Java应用程序中使用最为频繁的内存区域,因为它负责存储几乎所有的对象实例。
二、JavaHeapSpace的具体含义
JavaHeapSpace特指堆内存中的一段空间,用于存储通过“new”关键字创建的对象实例。 这些对象在程序运行过程中会占用不同大小的内存空间,JVM会动态地管理这些对象的内存分配和回收。 当应用程序创建新的对象时,JVM会在堆内存中为其分配空间;当对象不再被引用时,其占用的内存会被废品回收器释放,以便后续的对象可以使用这些空间。
三、OutOfMemoryError与JavaHeapSpace的关系
如果应用程序使用的内存超过了JVM为其分配的最大堆内存空间,就会抛出OutOfMemoryError错误。 这时,开发者需要关注JavaHeapSpace的使用情况,可能需要调整JVM的启动参数来增大堆内存的大小,或者检查程序中是否存在内存泄漏的问题。 通过合理地管理和优化JavaHeapSpace的使用,可以有效避免OutOfMemoryError错误的发生,保证程序的稳定运行。
综上所述,JavaHeapSpace是Java程序中非常重要的一个概念,它涉及到对象的内存分配和回收,对于Java开发者来说,理解并合理地管理JavaHeapSpace是确保程序稳定运行的关键之一。
java中的内存溢出和内存泄漏内存溢出:对于整个应用程序来说,JVM内存空间,已经没有多余的空间分配给新的对象。 所以就发生内存溢出。 内存泄露:在应用的整个生命周期内,某个对象一直存在,且对象占用的内存空间越来越大,最终导致JVM内存泄露,比如:缓存的应用,如果不设置上限的话,缓存的容量可能会一直增长。 静态集合引用,如果该集合存放了无数个对象,随着时间的推移也有可能使容量无限制的增长,最终导致JVM内存泄露。 内存泄露,是应用程序中的某个对象长时间的存活,并且占用空间不断增长,最终导致内存泄露。 是对象分配后,长时间的容量增长。 内存溢出,是针对整个应用程序的所有对象的分配空间不足,会造成内存溢出。 内存泄漏内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 内存泄漏与许多其他问题有着相似的症状,并且通常情况下只能由那些可以获得程序源代码的程序员才可以分析出来。 然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,即使严格意义上来说这是不准确的。 一般我们常说的内存泄漏是指堆内存的泄漏。 堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。 应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。 内存泄漏可以分为4类: 1. 常发性内存泄漏。 发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。 2. 偶发性内存泄漏。 发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。 常发性和偶发性是相对的。 对于特定的环境,偶发性的也许就变成了常发性的。 所以测试环境和测试方法对检测内存泄漏至关重要。 3. 一次性内存泄漏。 发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。 比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。 4. 隐式内存泄漏。 程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。 严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。 但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。 所以,我们称这类内存泄漏为隐式内存泄漏。 简单点: 内存泄漏就是忘记释放使用完毕的内存,让下次使用有一定风险。 内存溢出就是一定的内存空间不能装下所有的需要存放的数据,造成内存数据溢出。 主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解GC回收机制;重点关注如何去监控和发现内存问题;此外分析出问题还要如何解决内存问题。 下面就开始本篇的内容:第一部分 概念众所周知,java中的内存由java虚拟机自己去管理的,他不像C++需要自己去释放。 笼统地去讲,java的内存分配分为两个部分,一个是数据堆,一个是栈。 程序在运行的时候一般分配数据堆,把局部的临时的变量都放进去,生命周期和进程有关系。 但是如果程序员声明了static的变量,就直接在栈中运行的,进程销毁了,不一定会销毁static变量。 另外为了保证java内存不会溢出,java中有废品回收机制。 ()即废品收集机制是指jvm用于释放那些不再使用的对象所占用的内存。 java语言并不要求jvm有gc,也没有规定gc如何工作。 废品收集的目的在于清除不再使用的对象。 gc通过确定对象是否被活动对象引用来确定是否收集该对象。 而其中,内存溢出就是你要求分配的java虚拟机内存超出了系统能给你的,系统不能满足需求,于是产生溢出。 内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。 一直下去,程序也逐渐无内存使用,就会溢出。 第二部分 原理JAVA废品回收及对内存区划分在Java虚拟机规范中,提及了如下几种类型的内存空间:◇ 栈内存(Stack):每个线程私有的。 ◇ 堆内存(Heap):所有线程公用的。 ◇ 方法区(Method Area):有点像以前常说的“进程代码段”,这里面存放了每个加载类的反射信息、类函数的代码、编译时常量等信息。 ◇ 原生方法栈(Native Method Stack):主要用于JNI中的原生代码,平时很少涉及。 而Java的使用的是堆内存,java堆是一个运行时数据区,类的实例(对象)从中分配空间。 Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,“废品回收”也是主要是和堆内存(Heap)有关。 废品回收的概念就是JAVA虚拟机(JVM)回收那些不再被引用的对象内存的过程。 一般我们认为正在被引用的对象状态为“alive”,而没有被应用或者取不到引用属性的对象状态为“dead”。 废品回收是一个释放处于”dead”状态的对象的内存的过程。 而废品回收的规则和算法被动态的作用于应用运行当中,自动回收。 JVM的废品回收器采用的是一种分代(generational )回收策略,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。 这样就不需要每次GC都将内存中所有对象都检查一遍,这种策略有利于实时观察和回收。 (Sun JVM 1.3 有两种最基本的内存收集方式:一种称为copying或scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。 这种方法有效率,但需要有一定的空闲内存,拷贝也有开销。 这种方法用于minor collection。 另外一种称为Mark-compact,将活着的对象标记出来,然后搬迁到一起连成大块的内存,其他内存就可以回收了。 这种方法不需要占用额外的空间,但速度相对慢一些。 这种方法用于major collection. )一些对象被创建出来只是拥有短暂的生命周期,比如 iterators 和本地变量。 另外一些对象被创建是拥有很长的生命周期,比如持久化对象等。 废品回收器的分代策略是把内存区划分为几个代,然后为每个代分配一到多个内存区块。 当其中一个代用完了分配给他的内存后,JVM会在分配的内存区内执行一个局部的GC(也可以叫minor collection)操作,为了回收处于“dead”状态的对象所占用的内存。 局部GC通常要比Full GC快很多。 JVM定义了两个代,年轻代(yong generation)(有时称为“nursery”托儿所)和老年代(old generation)。 年轻代包括 “Eden space(伊甸园)”和两个“survivor spaces”。 虚拟内存初始化的时候会把所有对象都分配到 Eden space,并且大部分对象也会在该区域被释放。 当进行 minor GC的时候,VM会把剩下的没有释放的对象从Eden space移动到其中一个survivor spaces当中。 此外,VM也会把那些长期存活在survivor spaces 里的对象移动到 老生代的“tenured” space中。 当 tenured generation 被填满后,就会产生Full GC,Full GC会相对比较慢因为回收的内容包括了所有的 live状态的对象。 pemanet generation这个代包括了所有java虚拟机自身使用的相对比较稳定的数据对象,比如类和对象方法等。 关于代的划分,可以从下图中获得一个概况:第三部分 总结内存溢出主要是由于代码编写时对某些方法、类应用不合理,或者没有预估到临时对象会占用很大内存量,或者把过多的数据放入JVM缓存,或者性能压力大导致消息堆积而占用内存,以至于在性能测试时,生成庞大数量的临时对象,GC时没有做出有效回收甚至根本就不能回收,造成内存空间不足,内存溢出。 如果编码之前,对内存使用量进行预估,对放在内存中的数据进行评估,保证有用的信息尽快释放,无用的信息能够被GC回收,这样在一定程度上是可以避免内存溢出问题的。
尽管java虚拟机和废品回收机制管理着大部分的内存事务,但是在java软件中还是可能存在内存泄漏的情况。 的确,在大型工程中,内存泄漏是一个普遍问题。 避免内存泄漏的第一步,就是要了解他们发生的原因。 这篇文章就是要介绍一些常见的缺陷,然后提供一些非常好的实践例子来指导你写出没有内存泄漏的代码。 一旦你的程序存在内存泄漏,要查明代码中引起泄漏的原因是很困难的。 同时这篇文章也要介绍一个新的工具来查找内存泄漏,然后指明发生的根本原因。 这个工具容易上手,可以让你找到产品级系统中的内存泄漏。 废品回收(GC)的角色虽然废品回收关心着大部分的问题,包括内存管理,使得程序员的任务显得更加轻松,但是程序员还是可能犯些错误导致内存泄漏问题。 GC(废品回收)通过递归对所有从“根”对象(堆栈中的对象,静态数据成员,JNI句柄等等)继承下来的引用进行工作,然后标记所有可以访问的活动着的对象。 而这些对象变成了程序唯一能够操纵的对象,其他的对象都被释放了。 因为GC使得程序不能够访问那些被释放的对象,所以这样做是安全的。 内存管理可以说是自动的,但是这并没有让程序员脱离内存管理问题。 比方说,对于内存的分配(还有释放)总是存在一定的开销,尽管这些开销对程序员来说是隐含的。 一个程序如果创建了很多对象,那么它就要比完成相同任务而创建了较少对象的程序执行的速度慢(如果其他的条件都相同)。 文章更多想说的,导致内存泄漏主要的原因是,先前申请了内存空间而忘记了释放。 如果程序中存在对无用对象的引用,那么这些对象就会驻留内存,消耗内存,因为无法让废品回收器验证这些对象是否不再需要。 正如我们前面看到的,如果存在对象的引用,这个对象就被定义为“活动的”,同时不会被释放。 要确定对象所占内存将被回收,程序员就要务必确认该对象不再会被使用。 典型的做法就是把对象数据成员设为null或者从集合中移除该对象。 注意,当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。 从更高一个层次看,这就是所有存在内存管的语言对内存泄漏所考虑的事情,剩余的对象引用将不再会被使用。 典型的泄漏既然我们知道了在java中确实会存在内存泄漏,那么就让我们看一些典型的泄漏,并找出他们发生的原因。 全局集合在大型应用程序中存在各种各样的全局数据仓库是很普遍的,比如一个JNDI-tree或者一个session table。 在这些情况下,注意力就被放在了管理数据仓库的大小上。 当然是有一些适当的机制可以将仓库中的无用数据移除。 可以有很多不同的解决形式,其中最常用的是一种周期运行的清除作业。 这个作业会验证仓库中的数据然后清除一切不需要的数据。 另一个办法是计算引用的数量。 集合负责跟踪集合中每个元素的引用者数量。 这要求引用者通知集合什么时候已经对元素处理完毕。 当引用者的数目为零时,就可以移除集合中的相关元素。 高速缓存高速缓存是一种用来快速查找已经执行过的操作结果的数据结构。 因此,如果一个操作执行很慢的话,你可以先把普通输入的数据放入高速缓存,然后过些时间再调用高速缓存中的数据。 高速缓存多少还有一点动态实现的意思,当数据操作完毕,又被送入高速缓存。 一个典型的算法如下所示:1. 检查结果是否在高速缓存中,存在则返回结果;2. 如果结果不在,那么计算结果;3. 将结果放入高速缓存,以备将来的操作调用。 这个算法的问题(或者说潜在的内存泄漏)在最后一步。 如果操作是分别多次输入,那么存入高速缓存的内容将会非常大。 很明显这个方法不可取。 为了避免这种潜在的致命错误设计,程序就必须确定高速缓存在他所使用的内存中有一个上界。 因此,更好的算法是:1. 检查结果是否在高速缓存中,存在则返回结果;2. 如果结果不在,那么计算结果;3. 如果高速缓存所占空间过大,移除缓存中旧的结果;4. 将结果放入高速缓存,以备将来的操作调用。 通过不断的从缓存中移除旧的结果,我们可以假设,将来,最新输入的数据可能被重用的几率要远远大于旧的结果。 这通常是一个不错的设想。 这个新的算法会确保高速缓存的容量在预先确定的范围内。 精确的范围是很难计算的,因为缓存中的对象存在引用时将继续有效。 正确的划分高速缓存的大小是一个复杂的任务,你必须权衡可使用内存大小和数据快速存取之间的矛盾。 另一个解决这个问题的途径是使用类来将对象放入高速缓存。 这个方法可以保证当虚拟机用完内存或者需要更多堆的时候,可以释放这些对象的引用。 类装载器Java类装载器创建就存在很多导致内存泄漏的漏洞。 由于类装载器的复杂结构,使得很难得到内存泄漏的透视图。 这些困难不仅仅是由于类装载器只与“普通的”对象引用有关,同时也和对象内部的引用有关,比如数据变量,方法和各种类。 这意味着只要存在对数据变量,方法,各种类和对象的类装载器,那么类装载器将驻留在JVM中。 既然类装载器可以同很多的类关联,同时也可以和静态数据变量关联,那么相当多的内存就可能发生泄漏。 定位内存泄漏常常地,程序内存泄漏的最初迹象发生在出错之后,在你的程序中得到一个OutOfMemoryError。 这种典型的情况发生在产品环境中,而在那里,你希望内存泄漏尽可能的少,调试的可能性也达到最小。 也许你的测试环境和产品的系统环境不尽相同,导致泄露的只会在产品中暴露。 这种情况下,你需要一个低负荷的工具来监听和寻找内存泄漏。 同时,你还需要把这个工具同你的系统联系起来,而不需要重新启动他或者机械化你的代码。 也许更重要的是,当你做分析的时候,你需要能够同工具分离而使得系统不会受到干扰。 一个OutOfMemoryError常常是内存泄漏的一个标志,有可能应用程序的确用了太多的内存;这个时候,你既不能增加JVM的堆的数量,也不能改变你的程序而使得他减少内存使用。 但是,在大多数情况下,一个OutOfMemoryError是内存泄漏的标志。 一个解决办法就是继续监听GC的活动,看看随时间的流逝,内存使用量是否会增加,如果有,程序中一定存在内存泄漏。 详细输出有很多办法来监听废品回收器的活动。 也许运用最广泛的就是以:-Xverbose:gc选项运行JVM,然后观察输出结果一段时间。 [memory] 10.109-10.235: GC K->K (K), 126.000 ms箭头后的值(在这个例子中 K)是废品回收后堆的使用量。 控制台观察这些无尽的GC详细统计输出是一件非常单调乏味的事情。 好在有一些工具来代替我们做这些事情。 The JRockit Management Console可以用图形的方式输出堆的使用量。 通过观察图像,我们可以很方便的观察堆的使用量是否伴随时间增长。 Figure 1. The JRockit Management Console管理控制台甚至可以配置成在堆使用量出现问题(或者其他的事件发生)时向你发送邮件。 这个显然使得监控内存泄漏更加容易。 内存泄漏探测工具有很多专门的内存泄漏探测工具。 其中The JRockit Memory Leak Detector可以供来观察内存泄漏也可以针对性地找到泄漏的原因。 这个强大的工具被紧密地集成在JRockit JVM中,可以提供最低可能的内存事务也可以轻松的访问虚拟机的堆。 专门工具的优势一旦你知道程序中存在内存泄漏,你需要更专业的工具来查明为什么这里会有泄漏。 而JVM是不可能告诉你的。 现在有很多工具可以利用了。 这些工具本质上主要通过两种方法来得到JVM的存储系统信息的:JVMTI和字节码仪器。 Java虚拟机工具接口(JVMTI)和他的原有形式JVMPI(压型接口,profiling Interface)都是标准接口,作为外部工具同JVM进行通信,搜集JVM的信息。 字节码仪器则是引用通过探针获得工具所需的字节信息的预处理技术。 通过这些技术来侦测内存泄漏存在两个缺点,而这使得他们在产品级环境中的运用不够理想。 首先,根据两者对内存的使用量和内存事务性能的降级是不可以忽略的。 从JVM获得的堆的使用量信息需要在工具中导出,收集和处理。 这意味着要分配内存。 按照JVM的性能导出信息是需要开销的,废品回收器在搜集信息的时候是运行的非常缓慢的。 另一个缺点就是,这些工具所需要的信息是关系到JVM的。 让工具在JVM开始运行的时候和它关联,而在分析的时候,分离工具而保持JVM运行,这显然是不可能的。 既然JRockit Memory Leak Detector是被集成到JVM中的,那么以上两种缺点就不再存在。 首先,大部分的处理和分析都是在JVM中完成的,所以就不再需要传送或重建任何数据。 处理也可以建立在废品回收器的基础上,即提高速度。 再有,内存泄漏侦测器可以同一个运行的JVM关联和分离,只要JVM在开始的时候伴随着 –Xmanagement选项(通过远程JMX接口允许监听和管理JVM)。 当工具分离以后,工具不会遗留任何东西在JVM中;JVM就可以全速运行代码就好像工具关联之前一样。 趋势分析让我们更深一步来观察这个工具,了解他如何捕捉到内存泄漏。 在你了解到代码中存在内存泄漏,第一步就是尝试计算出什么数据在泄漏——哪个对象类导致泄露。 The JRockit Memory Leak Detector通过在废品回收的时候,计算每个类所包含的现有的对象来达到目的。 如果某一个类的对象成员数目随着时间增长(增长率),那么这里很可能存在泄漏。 Figure 2. The trend analysis view of the Memory Leak Detector因为一个泄漏很可能只是像水滴一样小,所以趋势分析必须运行足够长的一段时间。 在每个短暂的时间段里,局部类的增加会使得泄漏发生推迟。 但是,内存事务是非常小的(最大的内存事务是由在每个废品回收时从JRockit向内存泄漏探测器发送的一个数据包组成的)。 内存事务不应该成为任何系统的问题——甚至一个在产品阶段全速运行的程序。 一开始,数字会有很大的跳转,随时间的推进,这些数字会变得稳定,而后显示哪些类会不断的增大。 寻找根本原因知道那些对象的类会导致泄露,有时候足够制止泄露问题。 这个类也许只是被用在非常有限的部分,通过快速的视察就可以找到问题所在。 不幸的是,这些信息是不够的。 比方说,经常导致内存泄漏的对象类,然而String类被应用于整个程序,这就变得有些无助。 我们想知道的是其他的对象是否会导致内存泄漏,好比上面提到的String类,为什么这些导致泄漏的对象还是在周围存在?哪些引用是指向这些对象的?如果列出所有引用String的对象,工作就会变得太大而没有实际意义。 为了限制数据的数量,我们可以通过类把他们编成一个组,这样我们就可以看到,那些其他类的对象会依然泄漏对象(String类)。 比如,将一个String类放入Hashtable,那里我们可以看到关联到String类的Hashtable入口。 从Hashtable入口向后运行,我们终于找到那些关联到String类的Hashtable对象(参看图三如下)。 Figure 3. Sample view of the type graph as seen in the tool向后工作自从开始我们就一直着眼于对象类,而不是单独的对象,我们不知道那个Hashtable存在泄漏。 如果我们可以找出所有的Hashtable在系统中有多大,我们可以假设最大的那个Hashtable存在泄漏(因为它可以聚集足够的泄漏而变得很大)。 因此,所有Hashtable,同时有和所有他们所涉及的数据,可以帮助我们查明导致泄露的精确的Hashtable。 Figure 4. Screenshot of the list of Hashtable objects and the size of the data they are holding live计算一个对象所涉及的数据的开销是非常大的(这要求引用图表伴随着那个对象作为根运行)而且如果对每一个对象都这样处理,就需要很多时间。 知道一些关于Hashtable内部的实现机制可以带来捷径。 在内部,一个Hashtable有一个Hashtable的数组入口。 数组的增长伴随着Hashtable中对象的增长。 因此,要找到最大的Hashtable,我们可以把搜索限制在寻找包含Hashtable引用入口的最大的数组。 这样就更快捷了。 Figure 5. Screenshot of the listing of the largest Hashtable entry arrays, as well as their sizes.向下深入当我们发现了存在泄漏的Hashtable的实例,就可以顺藤摸瓜找到其他的引用这些Hashtable的实例,然后用上面的方法来找到是那个Hashtable存在问题。 Figure 6. This is what an instance graph can look like in the tool.举个例子,一个Hashtable可以有一个来自MyServer的对象的引用,而MyServer包含一个activeSessions数据成员。 这些信息就足够深入代码找出问题所在。 Figure 7. Inspecting an object and its references to other objects找出分配点当发现了内存泄漏问题,找到那些泄漏的对象在何处是非常有用的。 也许没有足够的信息知道他们同其他相关对象之间的联系,但是关于他们在那里被创建的信息还是很有帮助的。 当然,你不会愿意创建一个工具来打印出所有分配的堆栈路径。 你也不会愿意在模拟环境中运行程序只是为了捕捉到一个内存泄漏。 有了JRockit Memory Leak Detector,程序代码可以动态的在内存分配出创建堆栈路径。 这些堆栈路径可以在工具中累积,分析。 如果你不启用这个工具,这个特征就不会有任何消耗,这就意味着时刻准备着开始。 当需要分配路径时,JRockit的编译器可以让代码不工作,而监视内存分配,但只对需要的特定类有效。 更好的是,当做完数据分析后,生成的机械代码会完全被移除,不会引起任何执行上的效率衰退。 Figure 8. The allocation stack traces for String during execution of a sample program总结内存泄漏查找起来非常困难,文章中的一些避免泄漏的好的实践,包括了要时刻记住把什么放进了数据结构中,更接近的监视内存中意外的增长。 我们同时也看到了JRockit Memory Leak Detector是如何捕捉产品级系统中的内存泄漏的。 该工具通过三步的方法发现泄漏。 一,通过趋势分析发现那些对象类存在泄漏;二,找出同泄漏对象相关的其他类;三,向下发掘,观察独立的对象之间是如何相互联系的。 同时,该工具也可以动态的,找出所有内存分配的堆栈路径。 利用这三个特性,将该工具紧紧地集成在JVM中,那么就可以安全的,有效的捕捉和修复内存泄漏了。
标签: 避免内存泄漏和性能瓶颈、 java服务器怎么搭建、 Java服务器内存管理详解、本文地址: https://yihaiquanyi.com/article/59612b7a940a4103918f.html
上一篇:Java服务器并发编程实践处理高并发请求的最...