根据JVM规范,JVM内存划分为
- 堆(heap)
- 栈(虚拟机栈,VM Stack)
- 本地方法栈(native method stack)
- 方法区(method area)
- 程序计数器(program counter register)
补充:
常量池
是方法区的一部分;- 在
堆
中划出一块,称之为非堆
或永久代
,用来实现方法区 - 线程私有:即线程隔离,每个线程有自己一份的,有
栈
、本地方法栈
、程序计数器
- 线程共享:
堆
、方法区
JDK1.7的堆划分为
- 新生代(young generation)
- 老年代(old generation)
- 永久代(permanent generation)
注:JDK1.8中取消了永久代,用metaspace替代,metaspace不属于堆内存,不属于JVM中的一部分,而属于本地内存,称之为直接内存
详细划分如下:
JDK1.7
heap
|-young
| |------------eden
| |-survivor
| |----from(s0)
| |----to(s1)
|-old
|-permanent
JDK1.8
heap
|-young
| |------------eden
| |-survivor
| |----from(s0)
| |----to(s1)
|-old
|-metaspace(直接内存,不属于堆)
简述java垃圾回收机制?
在Java中,程序员是不需要显式去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
如何判断对象是否存活?判断对象是否存活一般有两种方式
- 引用计数法
- 可达性分析
主流的JVM里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象间的互循环引用的问题。
引用计数法: 每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析: 通过一些“GCRoots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(ReferenceChain),当一个对象没有被GCRoots的引用链连接的时候,说明这个对象是不可用的。 GCRoots对象包括:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
- 方法区域中的类静态属性引用的对象。
- 方法区域中常量引用的对象。
- 本地方法栈中JNI(Native方法)的引用的对象。
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
当前商业虚拟机都是采用分代收集算法,新生代使用复制算法
,老年代使用标记-清除
或 标记-整理
算法。原因:新生代对象产生快销毁快,短生命周期使得存活少,只需要复制少量存活对象,因此使用复制算法
效率高并且还不产生内存碎片。而老年代的对象存活率高、对象生命周期长,复制算法的成本高,适合标记-清除或标记-整理算法。
标记-清除算法: 算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
缺点:1、效率低(两个过程效率不高) 2、内存空间产生碎片(可能导致分配大对象时找不到连续内存块而触发GC)
复制算法: 将内存划分为等大两半,每次只用其中一半,其中一半用完,则将存活对象复制到另一半中,然后清除旧的那半内存,更改一下引用。
优点:效率高,无内存碎片;
缺点:1、浪费一半的内存空间 2、在对象存活率较高时就要进行较多的复制操作,效率将会变低
标记-整理算法: (也有叫标记-压缩算法
的,英文是mark-compact) 跟标记清除算法
有点类似,先标记,之后不是清除,而是将存活的对象挪到一起,这样就不会产生碎片。
优点:1、不产生碎片 2、没有内存碎片后,对象创建内存分配也更快速了(可以使用TLAB进行分配)
缺点:1、依然是效率问题(两个过程效率不高)
分代收集算法: 就是将内存划分成几块,一般是把Java堆分为新生代和老年代,然后根据各自的特点采用最适当的收集算法。
在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,就选用复制算法,而老年代因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记清理”或者“标记整理”算法来进行回收
垃圾收集器有哪些?收集算法是方法论,收集器是其实现
7种收集器
- Serial收集器
- ParNew 收集器
- Parallel Scavenge 收集器
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
- G1收集器
相关概念:
1、并行和并发- 并行(parallel):指多条
垃圾回收线程
并行工作,但此时用户线程
仍然处于等待状态 - 并发(concurrent):指
垃圾回收线程
和用户线程
同时执行(但并不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾回收程序运行在另一个CPU上
这几个GC的概念其实很混乱了
- MinorGC:指发生在新生代的GC。新生代大多数对象频繁生成也频繁销毁,所以MinorGC很频繁,一般回收速度也很快。
- MajorGC:指发生在老年代的GC。出现了MajorGC常常会伴随至少一次的MinorGC,MajorGC一般会比MinorGC慢10倍
- FullGC:在周志明的《深入理解Java虚拟机》中,这个概念和MajorGC一样,但是参考了网上的资料,FullGC似乎比MajorGC的概念要大,包括
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
详解各收集器
收集器作用区是否Stop The World
串行/并行/并发算法其他说明Serial新生代Stop The World串行复制算法单线程收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作,停顿时间几十毫秒最多一百毫秒以内ParNew新生代Stop The World并行复制算法是Serial的多线程版本,除了使用多线程进行垃圾收集外,其余行为包括Serial收集器可用的所有控制参数、收集算法(复制算法)、Stop The World、对象分配规则、回收策略等与Serial收集器完全相同,两者共用了相当多的代码,采用复制算法
Parallel Scavenge新生代Stop The World并行复制算法特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput) , 主要适合在后台运算而不需要太多交互的任务Serial Old老年代Stop The World串行标记-整理单线程,它的工作流程与Serial收集器相同,只是算法不一样,适用于老年代Parallel Old老年代Stop The World并行标记-整理多线程,是Parallel Scavenge收集器的老年代版本, “吞吐量优先”收集器, Parallel Scavenge加Parallel Old 可以配套使用CMS(concurrent mark sweep)老年代也是要停顿的,只是停顿得比其他要短很多,具体参考说明1并发标记-清除是一种以获取最短回收停顿时间为目标的收集器, 并发收集、低停顿,G1(Garbage-First)新生代/老年代均可并发标记-整理+复制算法面向服务端应用,将来替换CMS
说明1:
CMS收集器工作的整个流程分为以下4个步骤:
- 初始标记(CMS initial mark):仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。
- 并发标记(CMS concurrent mark):进行GC Roots Tracing的过程,在整个过程中耗时最长。
- 重新标记(CMS remark):为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。此阶段也需要“Stop The World”。
- 并发清除(CMS concurrent sweep)
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
如何配置收集器?问题补充:要如何配置新生代和年老代的垃圾收集器?
新生代GC策略年老代GC策略说明组合1SerialSerial OldSerial和Serial Old都是单线程进行GC,特点就是GC时暂停所有应用线程。组合2SerialCMS+Serial OldCMS(Concurrent Mark Sweep)是并发GC,实现GC线程和应用线程并发工作,不需要暂停所有应用线程。另外,当CMS进行GC失败时,会自动使用Serial Old策略进行GC。组合3ParNewCMS使用-XX:+UseParNewGC选项来开启。ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量。可以使用-XX:ParallelGCThreads选项指定GC的线程数。如果指定了选项-XX:+UseConcMarkSweepGC选项,则新生代默认使用ParNew GC策略。组合4ParNewSerial Old使用-XX:+UseParNewGC选项来开启。新生代使用ParNew GC策略,年老代默认使用Serial Old GC策略。组合5Parallel ScavengeSerial OldParallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间),可见这会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。组合6Parallel ScavengeParallel OldParallel Old是Serial Old的并行版本 划分的新生代、老年代、永久区是如何使用的?新生代的垃圾回收采用的是复制算法。第一次GC时,Eden区的存活对象会被复制到S0区。此后每次进行GC时,Eden区和From区的存活对象都会被复制到To区。如果一个对象在经历了几次垃圾回收后仍然存活,那么它就会被复制到Old Generation(老年代),此过程称为Promotion。
老年代的对象是由新生代对象经过Promotion而来,基于前面列出的假设:“如果对象已存活一段时间,那它很可能会继续存活一段时间”,该区域的对象存活率普遍较高,因此一般采用Mark-Sweep或Mark-Compact算法。
永久代并不用来存储从老年代经过Promotion而来的对象,它存储的是元数据,包括已被虚拟机加载的类信息、常量、静态变量、方法等。
JVM参数?JVM参数分为三类,一种是-
开头的,如-server
、-jar
、-version
等,是所有JVM虚拟机都实现的;二是-X
开头的,是非标准参数;三是-XX
的,是非稳定参数,就是说可能随时取消
- 关于
-client
和-server
注意,貌似64位的JVM使用-client
是不生效的,因其只有server模式
-XX:NewRatio=4
表示新生代:老年代=1:4,注意不是4:1-XX:SurvivorRatioEden和Survivor的比值例如-XX:SurvivorRatio=4
表示eden和survivor的比值是2:4,为什么是2而不是1:4,是因为survivor有两个区,即s0和s1,这是需要注意的-XX:NewSize=< n >[g|m|k]设置年轻代的初始值和设置比例方式一样,这个是设置具体的值-XX:MaxNewSize=< n >[g|m|k]设置年轻代的最大值和设置比例方式一样,这个是设置具体的值-XX:MetaspaceSize=size设置metaspace初始大小JDK8的选项,在8里,-XX:PermSize
和-XX:MaxPermSize
会被忽略-XX:MaxMetaspaceSize=size设置metaspace最大值JDK8的选项,在8里,-XX:PermSize
和-XX:MaxPermSize
会被忽略-Xss每个线程的栈内存大小(很多地方说设置堆栈的大小,注意堆栈就是栈的意思)一般不需要改,可以指定单位k(K),m(M),g(G) , 除非代码不多,可以设置的小点,另外一个相似的参数是-XX:ThreadStackSize,这两个参数在1.6以前,都是谁设置在后面,谁就生效;1.6版本以后,-Xss设置在后面,则以-Xss为准,-XXThreadStackSize设置在后面,则主线程以-Xss为准,其它线程以-XX:ThreadStackSize为准。
垃圾收集器相关
选项参数详解-XX:+UseParallelGC选择垃圾收集器为并行收集器。此配置仅对年轻代有效。可以同时并行多个垃圾收集线程,但此时用户线程必须停止-XX:+UseParNewGC设置年轻代收集器ParNew-XX:ParallelGCThreadsParallel并行收集器的线程数-XX:+UseParallelOldGC设置老年代的并行收集器是ParallelOld-XX:+UseG1GC使用G1收集器-XX:MaxGCPauseMillis每次年轻代垃圾回收的最长时间(最大暂停时间)-XX:+UseAdaptiveSizePolicy设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开-XX:GCTimeRatio设置垃圾回收时间占程序运行时间的,百分比公式为1/(1+n)-XX:+ScavengeBeforeFullGCFull GC前调用YGC-XX:+UseConcMarkSweepGC使用CMS内存收集-XX:+AggressiveHeap试图是使用大量的物理内存长时间大内存使用的优化,能检查计算资源(内存, 处理器数量)至少需要256MB内存大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提升)-XX:CMSFullGCsBeforeCompaction由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生"碎片",使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理-XX:+CMSParallelRemarkEnabled降低CMS标记停顿-XX+UseCMSCompactAtFullCollection在FULL GC的时候, 对年老代的压缩,CMS是不会移动内存的, 因此, 这个非常容易产生碎片, 导致内存不够用, 因此, 内存的压缩这个时候就会被启用。 增加这个参数是个好习惯。可能会影响性能,但是可以消除碎片-XX:+UseCMSInitiatingOccupancyOnly使用手动定义初始化定义开始CMS收集,禁止hostspot自行触发CMS GC-XX:CMSInitiatingOccupancyFraction=70使用cms作为垃圾回收使用70%后开始CMS收集-XX:CMSInitiatingPermOccupancyFraction设置Perm Gen使用到达多少比率时触发-XX:+CMSIncrementalMode设置为增量模式-XX:CMSTriggerRatioCMSInitiatingOccupancyFraction = (100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100) 处罚cms收集的比例-XX:MinHeapFreeRatioJava堆中空闲量占的最小比例-XX:+CMSClassUnloadingEnabled如果你启用了CMSClassUnloadingEnabled ,垃圾回收会清理持久代,移除不再使用的classes。这个参数只有在 UseConcMarkSweepGC 也启用的情况下才有用。参数如下:
辅助排查问题类参数
选项参数详解-XX:+PrintGC输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs][Full GC 121376K->10414K(130112K), 0.0650971 secs]-XX:+PrintGCDetails输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs][GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]-XX:+PrintGCTimeStamps可与上面两个混合使用,-XX:+PrintGC:PrintGCTimeStamps打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用-XX:+PrintGCApplicationStoppedTime打印垃圾回收期间程序暂停的时间.可与上面混合使用-XX:+PrintGCApplicationConcurrentTime打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用-XX:+PrintHeapAtGC打印GC前后的详细堆栈信息-Xloggc:filename把相关日志信息记录到文件以便分析.与上面几个配合使用-XX:+PrintClassHistogram遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同-XX:+PrintTenuringDistribution查看每次minor GC后新的存活周期的阈值-XX:PrintHeapAtGC打印GC前后的详细堆栈信息
例子
- java -server -Xms512m -Xmx512m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m - XX:MaxTenuringThreshold=20 XX:CMSInitiatingOccupancyFraction=80 - XX:+UseCMSInitiatingOccupancyOnly
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
- java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
(略)
常问问题补充- 是否新生代、年老代、永久代都会进行GC?
大多数文章只是提到对新生代、年老代会有GC,永久代是否存在GC呢,应该是存在的,参考《深入理解Java虚拟机》第二版P42和P68的描述。
- 什么是 MinorGC、MajorGC 和 FullGC
概念比较混乱了,姑且认为MinorGC是对新生代,MajorGC和FullGC是一样的意思,都是指对年老代的垃圾回收。
- GC 的时候一定得
stop the world
吗? 并行GC是什么回事?
是的,严格来讲,所有GC收集器都会stop the world
,只是长短问题,有些收集器实现得比较好,只是在处理步骤的某些不耗时的步骤会停顿较短(如CMS和G1)
《深入理解Java虚拟机》
判断是否存活
判断是否存活、收集算法
JVM内存划分,JDK8、7、6,永久区、metaspace
模拟各种OOM活栈SOF异常,分析永久代和metaspace
模拟各种OOM活栈SOF异
分析字符串、常量池
【系列文章质量较高】收集器、MinorGC、MajorGC、FullGC介绍
Oracle官方关于JDK7的JVM描述(英文)