按照对象的生存与回收进行划分:
首先明确,这一部分都是在jvm的堆上进行操作的,jvm对所有的线程提供同一个堆供其进行操作,而为每一个线程单独分配一个栈供其使用。
所以堆内存中所有的数据是线程们都可以进行操作的,而且也因为jvm只提供了一个堆内存,才有了jvm独特的GC机制和堆内存的划分。(图来自 https://www.cnblogs.com/ygj0930/p/6522828.html) :
堆内存大致可以分为新生代、老年代、永久代:
1,新生代
新生代又可以被细化为Eden、S1、S2三个区域,如上图可以看到。Eden和生存区(Survivor)的大小比例约为8:1。当我们在程序中创建出对象,一般情况下都会放在eden中。因为eden中的对象创建和失效十分频繁,所以经常会触发MinorGC。
jvm提供了多种回收方法。现在的商业虚拟机一般都使用复制清除算法进行新生代的GC工作,主要特点就是利用新生代的分区特点,在进行GC时,将Eden中的还存活的对象和S1(举例)中还存活的对象都复制到S2区域中,然后清除Eden和S1的全部数据,下次发生GC时,就将Eden和S2中的对象全部复制到S1中。
这样的情况适用于新生代对象存活率比较低的情况,如果对象存活率过高,S2/S1的内存空间不够容纳怎么办,复制清楚算法有一个内存担保的机制,在S1/S2的内存空间不足时,将向其他内存空间(一般都是老年代)借用空间进行对象的存储,这一过程称为分配担保。但是这样的机制也有问题,如果每次MinorGC都会进行内存担保,势必使老年代的空间迅速被填满,迫使老年代频繁的进行MajorGC,MajorGC较MinorGC效率和回收率都比较低,原本就不是会被频繁触发的GC,频繁触发自然会使程序效率降低(MajorGC会发生比较长时间的 stop the world)。
针对新生代对象生存率比较高的情况,jvm也提供了其他的MinorGC算法进行处理,如标记-整理算法。
2,老年代
jvm虚拟机采取了分代收集的思想来管理内存,即对不同的区域实行不同的回收策略。这样,区分哪些对象应该进入新生代和老年代、哪些对象需要从新生代过渡到老年代就十分重要了。
jvm对象为每一个对象都定义了一个年龄计数器,当对象出生在eden,且在一次GC中存活,并S1/S2区域有足够空间容纳它的话,年龄计数器就会+1,(为什么强调要出生在eden?如果出生在老年代的话,还要计数器干啥。为啥要强调有空间容纳?如果没空间的话,都被担保到老年代了,还要计数器干啥。) 当计数器年龄达到一定程度后,jvm默认是15,对象就会从新生代转移到老年代中。我们也可以通过jvm的配置文件中 -XX:MaxTenuringThreshold参数来进行配置这个程度。
当老年代空间不足时(大对象被创建,eden放不下/担保对象进入老年代),会触发MajorGC。MajorGC和MinorGC都会触发一种叫做STW(stop the world)的动作,主要作用是暂停大部分jvm上正在运行的线程,对内存中的对象进行扫描,对无用的对象进行清除。MinorGC比MajorGC暂停的时间要短。原因一,MinorGC的清除算法复杂度低(复制清除,相当于一种空间换时间的做法),MajorGC一般使用标记-清除或者标记-整理算法(没空间用复制算法),而且MajorGC中对象存活率高,扫描更慢。频繁的MajorGC会导致系统频繁停顿,影响效率。
除了MajorGC和MinorGC,还有一种叫做FullGC的垃圾收集动作,FullGC主要被触发的情况有:System.gc()/年代晋升失败/避免晋升失败的新生代内存与老年代内存的比较/CMS。
触发了FullGC将对新生代和老年代都进行垃圾收集操作
3,永久代
永生代在JDK1.8中,被元数据区取代(metaspace)。java8之前,hotspot使用在内存中划分出一块区域来存储类的元信息、类变量以及内部字符串(interned string)等内容,称之为永生代,把它作为方法区来使用。
补充一点JVM在内存分配上面的调优方法:
虚拟机通过参数的配置可以达到一定的调优目的,主要方法是修改tomcat/bin/catalina.bat文件内容。
1.-XX:MaxTenuringThreshold 调整新生代晋升老年代的年龄计数器大小,默认为15
2.-Xmx 设置jvm堆的大小
3.-Xmn 设置新生代大小 老年代大小 = 堆大小 - 新生代大小
4.-XX:PermSize 设置永生代大小(1.8之后还有这个吗?)