JVM内存简析
1.程序计数器:
这是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器,线程私有。
2.Java虚拟机栈:
它是Java方法执行的内存模型,每一个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程,线程私有。
3.本地方法栈:
和虚拟机栈类似,本地方法栈用于执行本地方法,线程私有。
4.Java堆:
该区域存在的唯一目的就是存放对象,几乎应用中所有的对象实例都在这里分配内存,所有线程共享。
5.方法区:
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,所有线程共享。
内存分代:
一个应用启动,操作系统会给它分配一个初始的内存大小,这部分内存大部分应该属于堆内存,JVM为了更好地利用管理这部分内存,对该区域作了划分,一部分成为新生代,另一部分成为老年代。
一开始对象的创建都发生在新生代,随着对象的不断创建,如果新生代没有空间创建新对象,将会发生GC,这时的GC,位于新生代的对象每经过一次Minor GC后,如果这个对象没有被回收,则为自己的标记数加1,这个标记数用于标识这个对象经历了多少次的Minor GC,对于Sun 的Hotspot虚拟机,如果这个次数超过15,该对象才会被移动到老年代。
随着时间的推移,如果老年代也没有足够的空间容纳对象,老年代也会试着发起GC,这时的GC被称为Full GC。
相比Minor GC,Full GC 发生的次数比较少,但是每发生一次Full GC ,整个堆内存区域都需要执行一次垃圾回收,这对程序性能造成的影响比Minor GC大很多。所以我们应该尽量避免或者减少Full GC 的发生。
同时,在堆内存区域,发生最多的GC情形就是新生代的Minor GC了,因为所有的对象会优先去新生代开辟空间,所以这块内存变化很快,只有内存不够用,就会发生GC,但是一般的Minor GC执行比Full GC快很多。因为新生代和老年代的垃圾回收算法不一样。
垃圾回收算法:
标记-清除 算法 (Mask-Sweep)
这是最基础的收集算法,算法分为“标记”和“清除”两个阶段:
首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。(之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。)
他的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前出发另一次垃圾回收动作。
复制算法(Copying)
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免太高了一点。但是这种算法的效率相当高,所以,现在的商业虚拟机都采用这种收集算法来回收新生代。为什么新生代可以使用复制算法呢?IBM 有专门研究表明,新生代中的对象 98% 都是朝生夕死,所以就不需要按照1:1的比例来划分内存空间。这里鉴于此,新生代采用了如下的划分策略。
现在把新生代再划分为三部分,一块较大的 Eden(伊甸园) 和两块较小的 Survivor(幸存者) 区域。
当回收时,将 Eden 和 Survivor 中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间。HotSpot 虚拟机默认Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被“浪费”的。这样清理完成后,原来的 Survivor 就空了,并一直保持为空,直到下次 Minor GC 时,它再作为存活对象的盛放地。两个 Survivor 就这样轮流当做 GC 过程中新生代存活对象的中转站。
但是,如果使用复制算法的内存区域有大量的存活对象时,复制算法就会变得捉襟见肘,这时需要更大的 Survivor 区用于盛放那些存活对象,甚至可能需要 1:1的比例。所以针对堆内存区域的老年代,就有了下面的算法。
标记-整理算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。这种方法避免了碎片的产生,同时也不需要一块额外的内存空间,对于老年代会比较合适。但是相比复制算法,虽然该算法占用的内存空间少,但是耗费的垃圾回收时间会比复制算法久,所以上面也说了我们应该尽量避免或者减少 Full GC 的发生。这两种算法用精炼的语言描述就是
复制算法:用空间换时间
标记-整理算法:用时间换空间
一句话 鱼与熊掌不可兼得,但是针对新生代和老年代,他们都是最佳的选择。
总结
简单梳理一下文中讲到的一些知识点
为了更好的管理堆内存,该区域分为新生代和老年代。
新生代发生垃圾回收要比老年代频繁。
新生代发生的垃圾回收成为 Minor GC;老年代发生的 GC 成为 Full GC。
新生代使用复制算法进行垃圾回收;老年代使用标记-整理算法
为了更高效管理新生代的内存,按照复制算法,结合 IBM 的研究论证,新生代分为三块,一块比较大的 Eden 区和两块比较小的 Survivor 区,比例为 8:1:1