您的位置:首页 > 编程语言 > Java开发

Java内存分配及垃圾回收

2013-03-22 09:42 295 查看
keywords:堆, 栈,常量池,gc, finalize

内存分配

从编译原理讲,程序内存分配的三种方法:静态分配、栈式分配、堆式分配。

静态分配指在编译期间就能确定内存分配空间大小,要求没有可变数据结构(如可变数组)、嵌套结构和递归结构。

栈(堆栈)主要用来运行程序的,堆主要是用来存放对象的。栈要求知道程序的存储要求(一个变量占个栈空间),堆为可变数据结构分配存储空间(如对象实例、List)。栈先进后出,效率高。堆向操作系统申请分配和销毁空间,速度慢,但空间大小变化灵活(C/C++中所有变量、方法都在栈中,也可动态申请堆空间malloc)。在面向对象编程中,堆分配必不可少,因为每次new一个对象都要申请堆空间。堆中还分配一块作为常量池(常量池:http://blog.csdn.net/fsz521/article/details/8864925)。

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虚拟示例即一个java程序只存在一个堆空间和方法区)。;而Java stack, Program counter 和Native method stack是以线程为粒度的,每个线程独自拥有自己的部分。

垃圾回收

Sun的JVM Generational Collecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的Method Area,不属于Heap。



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区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

根据jdk自带的内存监测工具jconsole,JVM内存分为堆内存和非堆内存。堆内存中有Eden Space、Survivor Space、Old Gen,非堆内存有Code Cache和Perm Gen。

关于内存管理的一些建议——

1、手动将生成的无用对象,中间对象置为null,加快内存回收。

2、对象池技术 如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。

3、JVM调优 通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上面两种办法都不能保证内存的回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。


Java内存分配及垃圾回收:http://hi.baidu.com/wolf_childer/item/c9f906d2c8229613d90e44d2


Java内存分配与垃圾回收的关系:http://www.cnblogs.com/qiengo/archive/2012/05/26/2519186.html

垃圾收集的算法:http://dev.yesky.com/178/2278678.shtml
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: