JVM的运行时内存也叫做JVM堆,从GC的角度可以将JVM分为新生代、老年代和永久代。
其中新生代默认占1/3堆内存空间,老年代默认占2/3堆内存空间,永久代占非常少的对内存空间。
新生代又分为Eden区、SurvivorFrom区和SurvivorTo区, Eden区默认占8/10新生代空间,SurvivorFrom区和SurvivorTo区默认分别占1/10新生代空间;Eden区最小占3/5新生代空间,SurvivorFrom区和SurvivorTo区分别占1/5新生代空间,如下图所示:
永久代
永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多会导致JVM内存不足而无法启动。
需要注意的是,在Java 8 中永久代已经被元数据区(也叫做元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,之和操作系统的内存有关。
在Java 8 中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用的内存空间决定。
原文链接:https://blog.csdn.net/qq_45886144/article/details/124083079
一、四大垃圾回收算法
1、引用计数器算法
原理其实很简单,给运行的对象添加一个引用计数器,每当有一个地方引用它时,计数器+1;当引用失效时,计数器就-1,任何时刻计数器为0的对象,就视作不可能再被使用。这一种方式,实现简单,逻辑也清晰,大部分的情况下,它都可以达到很好的效果,尽管这样,计数器算法还是存在但是的,但是它无法解决循环引用的场景,这也是主流Java虚拟机没有选用这一算法的原因。
2、复制算法(Copying)
为了解决标记清除算法的效率问题,有人提出了复制算法。它将可用内存一分为二,每次只用一块,当这一块内存不够用时,便触发 GC,将当前存活对象复制(Copy)到另一块上,以此往复。这种算法高效的原因在于分配内存时只需要将指针后移,不需要维护链表等。但它最大的问题是对内存的浪费,使用率只有 50%。
但这种算法在一种情况下会很高效:Java 对象的存活时间极短。据 IBM 研究,Java 对象高达 98% 是朝生夕死的,这也意味着每次 GC 可以回收大部分的内存,需要复制的数据量也很小,这样它的执行效率就会很高。
优点:没有标记和清除的过程,效率高;没有内存碎片,可以利用bump-the-pointer实现快速内存分配。
缺点:需要双倍空间。
3、标记清除算法(Mark Sweep)
该算法很简单,使用通过可达性分析方法标记出垃圾,然后直接回收掉垃圾区域。简单粗暴,即标记删除的对象,对其进行内存回收;它的一个显著问题是一段时间后,内存会出现大量碎片,导致虽然碎片总和很大,但无法满足一个大对象的内存申请,从而导致 OOM,而过多的内存碎片(需要类似链表的数据结构维护),也会导致标记和清除的操作成本高,效率低下。
缺点:两次扫描,耗时严重;会产生内存碎片。
可达性算法
此算法的核心思想:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。
4、标记压缩算法(Mark Sweep)
只是在标记清除的基础上,追加了碎片的散落问题,在清除之后进行了碎片的整理,但副作用是增了了GC的时间。