近期看了看Java内存泄露的一些案例,跟原来的几个哥们讨论了一下,深刻研究发现JVM里面仍是有很多之前不知道的细节,这里稍微剖析一下。先看一看JVM的内部结构——
如图所示,JVM主要包括两个子系统和两个组件。两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统;两个组件分别是Runtime data area (运行时数据区域)组件和Native interface(本地接口)组件。
Class loader子系统的做用:根据给定的全限定名类名(如 java.lang.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域)。Java程序员能够extends java.lang.ClassLoader类来写本身的Class loader。
Execution engine子系统的做用:执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不一样的JDK例如Sun 的JDK 和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。
Native interface组件:与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的而且再也不受虚拟机限制的世界,因此也很容易出现JVM没法控制的native heap OutOfMemory。
Runtime Data Area组件:这就是咱们常说的JVM的内存了。它主要分为五个部分——
一、Heap (堆):一个Java虚拟实例中只存在一个堆空间
二、Method Area(方法区域):被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,而后读入这个class文件内容并把它传输到虚拟机中。
三、Java Stack(java的栈):虚拟机只会直接对Java stack执行两种操做:以帧为单位的压栈或出栈
四、Program Counter(程序计数器):每个线程都有它本身的PC寄存器,也是该线程启动时建立的。PC寄存器的内容老是指向下一条将被执行指令的饿地址,这里的地址能够是一个本地指针,也能够是在方法区中相对应于该方法起始指令的偏移量。
五、Native method stack(本地方法栈):保存native方法进入区域的地址
以上五部分只有Heap 和Method Area是被全部线程的共享使用的;而Java stack, Program counter 和Native method stack是以线程为粒度的,每一个线程独自拥有本身的部分。
了解JVM的系统结构,再来看看JVM内存回收问题了——
Sun的JVM Generational Collecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不一样生命周期的对象使用不一样的算法。(基于对对象生命周期分析)
如上图所示,为Java堆中的各代分布。
1. Young(年轻代)
年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的而且此时还存活的对象,将被复制年老区(Tenured。须要注意,Survivor的两个区是对称的,没前后关系,因此同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。并且,Survivor区总有一个是空的。
2. Tenured(年老代)
年老代存放从年轻代存活的对象。通常来讲年老代存放的都是生命期较长的对象。
3. Perm(持久代)
用于存放静态文件,现在Java类、方法等。持久代对垃圾回收没有显著影响,可是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类。持久代大小经过-XX:MaxPermSize=进行设置。
举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,若是是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部份内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部份内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,通过屡次回收之后若是from区内存也分配完毕,就会也发生内存回收而后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收而后把幸存的对象拷贝至年老区。
一般咱们说的JVM内存回收老是在指堆内存回收,确实只有堆中的内容是动态申请分配的,因此以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是以前提到的Method Area,不属于Heap。
了解完这些以后,如下的转载一热衷于钻研技术的哥们Richen Wang关于内存管理的一些建议——
一、手动将生成的无用对象,中间对象置为null,加快内存回收。
二、对象池技术 若是生成的对象是可重用的对象,只是其中的属性不一样时,能够考虑采用对象池来较少对象的生成。若是有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提升了对象的复用率。
三、JVM调优 经过配置JVM的参数来提升垃圾回收的速度,若是在没有出现内存泄露且上面两种办法都不能保证内存的回收时,能够考虑采用JVM调优的方式来解决,不过必定要通过实体机的长期测试,由于不一样的参数可能引发不一样的效果。如-Xnoclassgc参数等。
推荐的两款内存检测工具
一、jconsole JDK自带的内存监测工具,路径jdk bin目录下jconsole.exe,双击可运行。链接方式有两种,第一种是本地方式如调试时运行的进程能够直接连,第二种是远程方式,能够链接以服务形式启动的进程。远程链接方式是:在目标进程的jvm启动参数中添加-Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false 1090是监听的端口号具体使用时要进行修改,而后使用IP加端口号链接便可。经过该工具能够监测到当时内存的大小,CPU的使用量以及类的加载,还提供了手动gc的功能。优势是效率高,速度快,在不影响进行运行的状况下监测产品的运行。缺点是没法看到类或者对象之类的具体信息。使用方式很简单点击几下就能够知道功能如何了,确实有不明白之处能够上网查询文档。
二、JProfiler 收费的工具,可是处处都有破解办法。安装好之后按照配置调试的方式配置好一个本地的session便可运行。能够监测当时的内存、CPU、线程等,能具体的列出内存的占用状况,还能够就某个类进行分析。优势不少,缺点太影响速度,并且有的类可能没法被织入方法,例如我使用jprofiler时一直没有备份成功过,总会有一些类的错误java
简单的概念:程序员
堆(Heap)和非堆(Non-heap)内存
堆内存分配
非堆内存分配
JVM内存限制(最大值)
同系列文章:算法
java虚拟机内存管理机制(一):http://blog.csdn.net/lengyuhong/archive/2010/10/20/5953544.aspx编程
java虚拟机内存管理机制(二):http://blog.csdn.net/lengyuhong/archive/2010/10/20/5953594.aspx数组
java虚拟机内存管理机制(三):http://blog.csdn.net/lengyuhong/archive/2010/10/19/5952008.aspx缓存