年轻代(Young) + 年老代(Tenured) + 持久代(Perm) 元空间(Metaspace)java
java虚拟机规范约定堆能够处于物理不连续的内存空间中,只要逻辑上是连续的便可,相似于磁盘空间。算法
堆内存 = 年轻代(Young) + 年老代(Tenured) 数组
基于对象生命周期分析,使用不一样的算法进行GC。多线程
年轻代中的对象基本都是朝生夕死(80%以上),因此年轻代的垃圾回收算法使用的是复制算法,基本思想: 将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另一块上面。复制算法不会产生内存碎片。 并发
通常而言: 年轻代 = Eden区 + 两个Survivor区(From和To) 默认比例为Eden:S0:S1==8:1:1。jsp
全部新生成的对象首先都是放在年轻代的,年轻代的目标就是尽量快速的收集掉那些生命周期短的对象,大部分对象在Eden区中生成。Survivor区是能够配置为多于两个的,这样能够增长对象在年轻代中的存在时间,减小被放到年老代的可能。函数
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。当Eden区满时进行Minor GC,Eden区中全部存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到必定值(年龄阈值,能够经过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。通过此次GC后,Eden区和From区已经被清空。性能
这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。无论怎样,Survivor区总有一个是空的,“From”与“To”是相对的。因此同一个区中可能同时存在从Eden复制过来的对象,和从前一个Survivor复制过来的对象。spa
标记(Mark)算法进行回收。操作系统
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。所以,能够认为年老代中存放的都是一些生命周期较长的对象。
用于存放静态文件,如Java静态类、方法等。持久代对垃圾回收没有显著影响,可是有些应用可能动态生成或者调用一些class,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类。经过-XX:MaxPermSize=n 进行设置。
在JDK8中, classe metadata(the virtual machines internal presentation of Java class),被存储在叫作Metaspace的native memory。
元空间的本质和永久代相似,都是对JVM规范中方法区的实现。最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。所以,默认状况下,元空间的大小仅受本地内存限制。
其实,移除永久代的工做从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没彻底移除,eg:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
永久代在JDK8中被彻底的移除了,因此永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。
public class StringOomMock { static String base = "string"; public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < Integer.MAX_VALUE; i++) { String str = base + base; base = str; list.add(str.intern()); // String类的 intern() 方法还可在运行期间把字符串放到字符串常量池中 } } }
这段程序以2的指数级不断的生成新的字符串,这样能够比较快速的消耗内存。经过 JDK 1.六、JDK 1.7 和 JDK 1.8 分别运行:
JDK 1.6 的运行结果:
JDK 1.7的运行结果:
JDK 1.8的运行结果:
结论:大体验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中。
"java.lang.OutOfMemoryError: PermGen space "这个异常中的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,然后者则是 JVM 规范的一种实现,而且只有 HotSpot 才有 “PermGen space”,仅仅由于HotSpot虚拟机的设计团队选择将GC分代收集扩展至方法区,这样垃圾收集就能像管理java堆同样管理永久代内存,省去专门为方法区编写内存管理代码工做。而对于其余类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并无“PermGen space”。
因为方法区主要存储类的相关信息,因此对于动态生成类的状况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的状况,容易出现永久代内存溢出。
永久代的缺陷:
移除后:
GC的性能获得了提高:
参照JEP122:http://openjdk.java.net/jeps/122,原文截取:
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.移除永久代是为融合HotSpot JVM与 JRockit VM而作出的努力,由于JRockit没有永久代,不须要配置永久代。
默认状况下,class metadata的分配仅受限于可用的native memory总量。可使用MaxMetaspaceSize来限制可为class metadata分配的最大内存。当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。设置MetaspaceSize为一个较高的值能够推迟垃圾收集的发生。
以栈或寄存器中的引用为起点,能够找到堆中的对象,又从这些对象找到对堆中其余对象的引用,这种引用逐步扩展,最终以null引用或者基本类型结束,这样就造成了一颗以Java栈中引用所对应的对象为根节点的一颗对象树,若是栈中有多个引用,则最终会造成多颗对象树。在这些对象树上的对象,都是当前系统运行所须要的对象,不能被垃圾回收。而其余剩余对象,则能够视为没法被引用到的对象,能够被当作垃圾进行回收。所以,垃圾回收的起点是一些根对象(java栈, 静态变量, 寄存器…)。而最简单的Java栈就是Java程序执行的main函数。这便是“标记-清除”的回收方式。
通常状况下,当新对象生成,而且在Eden申请空间失败时,就会触发Minor GC。对Eden区域进行GC,清除非存活对象,而且把尚且存活的对象移动到Survivor区;而后整理Survivor的两个区。由于大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,因此Eden区的GC会频繁进行。于是,通常在这里须要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
虚拟机给每一个对象定义了一个对象年龄(Age)计数器。若是对象在 Eden 出生并通过第一次 Scavenge GC 后仍然存活,而且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Scavenge GC,年龄就增长 1 岁,当它的年龄增长到必定程度(默认为 15 岁)时,就会晋升到老年代中。对象晋升老年代的年龄阈值,能够经过参数 -XX:MaxTenuringThreshold 来设置。
清理老年代。可是因为不少MojorGC 是由MinorGC 触发的,因此有时候很难将MajorGC 和MinorGC区分开。
对整个堆进行整理,包括Young、Tenured和Perm。Full GC由于须要对整个对进行回收,因此很慢,所以应该尽量减小Full GC的次数。在对JVM调优的过程当中,很大一部分工做就是对于FullGC的调节。有以下缘由可能致使Full GC:
串行收集:使用单线程处理全部垃圾回收工做;
并行收集:并行收集使用多线程处理垃圾回收工做,于是速度快,效率高。并且理论上CPU数目越多,越能体现出并行收集器的优点。
并发收集: 能够保证大部分工做都并发进行(应用不中止),垃圾回收只暂停不多的时间 。相对于串行收集和并行收集而言,前面两个在进行垃圾回收工做时,须要暂停整个运行环境,而只有垃圾回收程序在运行,所以,系统在垃圾回收时会有明显的暂停,并且暂停时间会由于堆越大而越长。
eg.
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。此值能够设置与-Xmx相同,以免每次垃圾回收完成后JVM从新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小= 年轻代大小 + 年老代大小 + 持久代大小。持久代通常固定大小为64m,因此增大年轻代后,将会减少年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每一个线程的堆栈大小。JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入年老代。对于年老代比较多的应用,能够提升效率。若是将此值设置为一个较大值,则年轻代对象会在Survivor区进行屡次复制,这样能够增长对象再年轻代的存活时间,增长在年轻代即被回收的概论。