首先是一些java,jvm内存的分布情况,然后是具体每块区域的内存回收机制。
java不同于c++等语言由计算机直接编译文件,java编译class文件后生成bytecode文件,然后由不同平台的jvm进行bytecode的编译,所以java可以实现跨平台。
jvm类似于一个虚拟的计算机系统,有虚拟的硬件,寄存器,堆,栈等。
jvm的内存区划分为,栈(虚拟机栈,本地栈),堆,方法区(永久区),程序计数器等。
程序计数器是用于指示当前线程所执行的字节码(bytecode)执行到了第几行,非常小的内存区域,而且不会发生内存溢出。
方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。
值得一提的是,运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。
还记得之前提到的java**编程内存优化**的小技巧中说的,如果后面还会用到“abc”,String str=“abc”优于new String(”abc“),原理就是利用了方法区的运行常量池。
栈分为虚拟机栈和本地方法栈,栈都是线程私有的。
虚拟机栈(JVM Stack):一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。
局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占 用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定 好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。
本地方法栈和虚拟机栈类似,不同的是,虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的
堆是JVM所管理的内存中最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存。
Java内存分配和回收的机制概括的说,就是:分代分配,分代回收。对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。
年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区和两个存活区(Survivor 0 、Survivor 1),分配机制是“停止-复制(Stop-and-copy)”分配过程如下:
1.刚创建的对象(如果不是特别大,或者没有超过Eden的剩余空间,否则会直接分配到老年代),会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;
2.当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);
此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0;
3.当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。
4.当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代。
年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行Major GC,也叫 Full GC,如果年轻代发生Minor GC时,虚拟机检查晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,直接就会触发一次Full GC。
如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。
在年老代中维护了一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,如果对象没有被年老代引用就可以清除了,被引用的继续存活,直到存活次数够长(比如15次)复制到年老代。一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。
永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
类的所有实例都已经被回收
加载类的ClassLoader已经被回收
类对象的Class对象没有被引用(即没有通过反射引用该类的地方)
http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html
http://iamzhongyong.iteye.com/blog/1333100
http://www.importnew.com/2057.html