jvm 大致内存划分及GC堆回收

很多东西不整理,过段时间就会忘记。此文仅为自己学习整理,如有不足,请指正

 一、JVM内存划分 

    jvm内存分为 :堆、栈(虚拟机栈、本地方法栈)、方法区(方法区中包含常量池)、程序计数器。

                            其中,堆和方法区是所有线程共享数据区域,栈和程序计数器是线程私有。

堆:对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域

栈:栈中只保存基础数据类型的对象和自定义对象的引用

方法区:用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行                           时常量池中

二、GC堆回收

1. 堆的分代:堆可以分为新生代、老年代和永久代

(1)新生代

新生代一般占据堆的1/3空间,分为三部分:Eden区,from servivor 区,to servivor区

Eden区:是Java对象的出生地(如果对象占用空间过大,好像是直接分配到老年代),当此区域空间不够时,会触发MinorGC

from servivor 区:上一次 GC 的幸存者,作为这一次 GC 的被扫描者

to servivor区:保留了一次 MinorGC 过程中的幸存者。

MinorGC的过程:采用复制算法,当发生MinorGC时,把Eden和From区域中存活的对象复制到To区域中(如果有达到老年代标准的对象则放到老年代区),并把Eden和From区清空,然后把To区域和From区域互换,使此次MinorGC存活下来的对象成为下次GC是扫描的对象,三个区域默认空间比值为:Eden:From:To = 8:1:1

(2)老年代

老年代中主要存放程序中生命周期较长的对象,老年代相对是比较稳定的,所以不会频繁的发生GC回收(老年代对应的GC回收是MajorGC),一般在进行MajorGC之前,是进行了MinorGC,或者创建较大的对象,导致有新的对象进入老年代区域,发现内存空间不够时,触发MajorGC

MajorGC:采用标记清除算法,首先扫描老年代,标记所有存活的对象,然后对没有标记的对象进行回收。因为需要先扫描所有对象再进行回收,所以耗时较长,且会产生内存碎片,一般需要进行合并。老年代空间满了装不下新的对象时,会报OOM(Out of Memory)异常。

(3)永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常

Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制

2. GC回收算法

(1)标记清除算法(Mark-Sweep):

最基础的垃圾回收算法,顾名思义,分为两部分,标记和清除。标记阶段标记出所有存活的对象,清除阶段回收所有没有标记的对象,该算法最大的问题是内存碎片化严重,内存空间不连续,导致后续可能发生大对象找不到可用空间的问题

(2)复制算法:

按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用
的内存清掉。这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,此算法的效率会大大降低

(3)标记整理算法(Mark-Compact):

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记之后并不进行回收,而是将存活对象移向内存的一端,然后清除端边界外的对象

(4)分代收集算法:

目前大部分JVM所有采用的是此方法,主要是针对不同周期的对象采用不同的策略。一般把堆分为老年代和新生代,因为老年代相对来说比较稳定,每次只有少量对象需要回收,所以可以采用标记整理算法,新生代则每次垃圾回收需要回收的对象较多,可以采用复制算法,复制存活的少量对象。

猜你喜欢

转载自blog.csdn.net/z1040141848/article/details/88893939
今日推荐