JVM 内存区域算法
一、程序计数器.net
这是一块较小的内存空间,它的做用能够看作是当前线程所执行的字节码的行号指示器,指的是上次代码被执行的地方,线程私有。线程
二、Java 虚拟机栈指针
它是 Java方法执行的内存模型,每个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程,线程私有。对象
三、本地方法栈blog
跟虚拟机栈相似,不过本地方法栈用于执行本地方法,线程私有。生命周期
四、Java 堆内存
该区域存在的惟一目的就是存放对象,几乎应用中全部的对象实例都在这里分配内存,全部线程共享。字符串
五、方法区get
它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,全部线程共享。
线程私有的区域随着线程的结束就没有了,没有垃圾回收;gc操做的地方是在全部线程共享的区域。
内存分代
为何要分代?
堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,咱们程序全部的对象实例都存放在堆内存中。给堆内存分代是为了提升对象内存分配和垃圾回收的效率。试想一下,若是堆内存没有区域划分,全部的新建立的对象和生命周期很长的对象放在一块儿,随着程序的执行,堆内存须要频繁进行垃圾收集,而每次回收都要遍历全部的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响咱们的GC效率,这简直太可怕了。
有了内存分代,状况就不一样了,新建立的对象会在新生代中分配内存,通过屡次回收仍然存活下来的对象存放在老年代中,静态属性、类信息等存放在永久代中,新生代中的对象存活时间短,只须要在新生代区域中频繁进行GC,老年代中对象生命周期长,内存回收的频率相对较低,不须要频繁进行回收,永久代中回收效果太差,通常不进行垃圾回收,还能够根据不一样年代的特色采用合适的垃圾收集算法。分代收集大大提高了收集效率,这些都是内存分代带来的好处。
内存分代划分
Java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念,它采用永久代的方式来实现方法区,其余的虚拟机实现没有这一律念,并且HotSpot也有取消永久代的趋势,在JDK 1.7中HotSpot已经开始了“去永久化”,把本来放在永久代的字符串常量池移出。永久代主要存放常量、类信息、静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。内存分代示意图以下:
新生代(Young)
新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集通常能够回收70% ~ 95% 的空间,回收效率很高。
HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1。划分的目的是由于HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减小浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
GC开始时,对象只会存在于Eden区和From Survivor区,To Survivor区是空的(做为保留区域)。GC进行时,Eden区中全部存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着, From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,无论怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,须要依赖老年代进行分配担保,将这些对象存放在老年代中。
老年代(Old)
在新生代中经历了屡次(具体看虚拟机配置的阀值)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,并且回收的速度也比较慢。
永久代(Permanent)
永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而言,Java虚拟机规范指出能够不进行垃圾收集,通常而言不会进行垃圾回收。
Minor GC 和 Full GC的区别
新生代GC(Minor GC):Minor GC指发生在新生代的GC,由于新生代的Java对象大多都是朝生夕死,因此Minor GC很是频繁,通常回收速度也比较快。当Eden空间不足觉得对象分配内存时,会触发Minor GC。
老年代GC(Full GC/Major GC):Full GC指发生在老年代的GC,出现了Full GC通常会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程当中重新生代进入老年代),好比:分配担保失败。Full GC的速度通常会比Minor GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。
垃圾回收算法
标记-清除算法(Mark-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
尽量的避免或者减小垃圾回收
本文转载至:https://blog.csdn.net/sumj7011/article/details/78087421