目录
java虚拟机内存管理机制(一):JVM内存管理总结【分享】 收藏 1
java虚拟机内存管理机制(二):了解JVM的内存管理与垃圾回收 收藏 4
java虚拟机内存管理机制(三):我主管写的一些jvm内存管理知识 收藏 7
java虚拟机内存管理机制(一):JVM内存管理总结【分享】 收藏
近期看了看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的内存了。它主要分为五个部分——
1、Heap (堆):一个Java虚拟实例中只存在一个堆空间
2、Method Area(方法区域):被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
3、Java Stack(java的栈):虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈
4、Program Counter(程序计数器):每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
5、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=进行设置。
java类的概念举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。
通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的Method Area,不属于Heap。
了解完这些之后,以下的转载一热衷于钻研技术的哥们Richen Wang关于内存管理的一些建议——
1、手动将生成的无用对象,中间对象置为null,加快内存回收。
2、对象池技术 如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。
3、JVM调优 通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上面两种办法都不能保证内存的回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。
推荐的两款内存检测工具
1、jconsole JDK自带的内存监测工具,路径jdk bin目录下,双击可运行。连接方式有两种,第一种是本地方式如调试时运行的进程可以直接连,第二种是远程方式,可以连接以服务形式启动的进程。远程连接方式是:在目标进程的jvm启动参数中添加-Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false 1090是监听的端口号具体使用时要进行修改,然后使用IP加端口号连接即可。通过该工具可以监测到当时内存的大小,CPU的使用量以及类的加载,还提供了手动gc的功能。优点是效率高,速度快,在不影响进行运行的情况下监测产品的运行。缺点是无法看到类或者对象之类的具体信息。使用方式很简单点击几下就可以知道功能如何了,确实有不明白之处可以上网查询文档。
2、JProfiler 收费的工具,但是到处都有破解办法。安装好以后按照配置调试的方式配置好一个本地的session即可运行。可以监测当时的内存、CPU、线程等,能具体的列出内存的占用情况,还可以就某个类进行分析。优点很多,缺点太影响速度,而且有的类可能无法被织入方法,例如我使用jprofiler时一直没有备份成功过,总会有一些类的错误
如图所示,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的内存了。它主要分为五个部分——
1、Heap (堆):一个Java虚拟实例中只存在一个堆空间
2、Method Area(方法区域):被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
3、Java Stack(java的栈):虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈
4、Program Counter(程序计数器):每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
5、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=进行设置。
java类的概念举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。
通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的Method Area,不属于Heap。
了解完这些之后,以下的转载一热衷于钻研技术的哥们Richen Wang关于内存管理的一些建议——
1、手动将生成的无用对象,中间对象置为null,加快内存回收。
2、对象池技术 如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。
3、JVM调优 通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上面两种办法都不能保证内存的回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。
推荐的两款内存检测工具
1、jconsole JDK自带的内存监测工具,路径jdk bin目录下,双击可运行。连接方式有两种,第一种是本地方式如调试时运行的进程可以直接连,第二种是远程方式,可以连接以服务形式启动的进程。远程连接方式是:在目标进程的jvm启动参数中添加-Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false 1090是监听的端口号具体使用时要进行修改,然后使用IP加端口号连接即可。通过该工具可以监测到当时内存的大小,CPU的使用量以及类的加载,还提供了手动gc的功能。优点是效率高,速度快,在不影响进行运行的情况下监测产品的运行。缺点是无法看到类或者对象之类的具体信息。使用方式很简单点击几下就可以知道功能如何了,确实有不明白之处可以上网查询文档。
2、JProfiler 收费的工具,但是到处都有破解办法。安装好以后按照配置调试的方式配置好一个本地的session即可运行。可以监测当时的内存、CPU、线程等,能具体的列出内存的占用情况,还可以就某个类进行分析。优点很多,缺点太影响速度,而且有的类可能无法被织入方法,例如我使用jprofiler时一直没有备份成功过,总会有一些类的错误
简单的概念:
堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给 自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法 的代码都在非堆内存中。
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给 自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法 的代码都在非堆内存中。
堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由 -Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆 直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由 -Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆 直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
JVM内存限制(最大值)
首先JVM内存限制于实际的最大物理内存(废话!呵呵),假设物理内存无限 大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是 2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了
首先JVM内存限制于实际的最大物理内存(废话!呵呵),假设物理内存无限 大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是 2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了
java虚拟机内存管理机制(二):了解JVM的内存管理与垃圾回收 收藏
Java语言具备GC(垃圾回收)的能力,内存管理不需要应用程序去过问,这很方便。但是,GC是怎么进行的,JVM的内存参数应该怎么调整,如何优化,往往我们不是太清楚。看过一些资料后,对Sun JVM的内存管理以及垃圾回收的机制大概有了一个概念,这里将这些资料归纳和翻译出来。本文内容主要基于Sun JVM 1.3.1,在后续版本中有不少优化措施,但是这些基本概念还是不变的。
这里假设大家对GC的概念和基本原理都已经了解,不详细叙述了。
当JVM进行GC的时候,是要消耗CPU资源和需要一定时间的,这会影响到程序的正常运行,因此需要尽可能减少GC消耗的时间。Java程序运行过程中,对象的生命周期有长有短,其中相当大部分是都是比较短命的,例如局部的对象一用完就可以回收了。在大多数情况下,只要能够及时回收这些短命对象的内存,就能够确保JVM有足够内存来分配给新的对象。因此JVM采用一种分代回收(generational collection) 的策略,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍。
Sun JVM 1.3 有两种最基本的内存收集方式:一种称为copying或scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。这种方法有效率,但需要有一定的空闲内存,拷贝也有开销。这种方法用于minor collection。另外一种称为mark-compact,将活着的对象标记出来,然后搬迁到一起连成大块的内存,其他内存就可以回收了。这种方法不需要占用额外的空间,但速度相对慢一些。这种方法用于major collection.
在JVM 1.3及以后的版本中,还有其他可选的内存收集方法,通过特定的参数来设定。例如:
增量式回收,每次只处理一小部分;替代单线程copying的多线程并行回收;替代mark-compact的concurrent mark-sweep回收等等。参考资料[4][5]中有更多描述。
JVM管理的内存,通常叫做堆(heap),可以用下面的图来描述。
JVM启动后,保留一段地址空间,这个空间的大小由-Xmx指定。这块空间的大小就是heap可能的最大值,但一开始不一定全都分配了物理内存,初始分配的heap大小由-Xms指定,如果-Xms小于-Xmx,剩余部分是virtual的,当需要的时候,再向OS申请。而且申请之后,是继续占用而不释放给该jvm以外的程序。比如你的jvm申请了1G的内存,刚开始用了200M,然后随着程序的进行,内存用到900M,然后进行垃圾回收,想释放一些内存给其他程序,这是不可以的,此时,jvm依然会保有着900M内存。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论