jvm

JAVA JVM的GC

已邀请:

http://nccloud.yytimes.com/uploads/answer/20230627/0f6109d13c7aec8ebfed7c62f67b4b0d.png

其中:程序计数器、虚拟机栈、本地方法栈这3个区域,都是随线程而生,随线程而死。不需要过多的考虑垃圾回收的问题

垃圾回收需要考虑的是:1.方法区 2.堆


一、内存分配与回收
1.1 方法区
属于永久代 perm generation(这里说的是JDK7,等到JDK8的时候没有永久代了,原本存在永久代的数据,一部分移到了java堆里面,一部分移到了本地内存里面(即元空间))

主要回收2个部分
1.1.1 废弃常量
没有引用的常量

1.1.2 无用类
无用类必须同时满足3个条件

1.该类所有的实例都已经被回收

2.加载该类的ClassLoader已经被回收

3.该类对应的java.lang.Class对象没有在任务地方被引用,无法在任何地方通过反射访问该类的方法

1.2 堆
堆空间分为新生代和老年代

1.2.1 新生代 Young generation
新生代中又分为:1个Eden区和2个Surivor区

Young generation = Eden(80%)+Survivor(10%)+Survivor(10%)

内存占比是8:1:1

比如设置参数 -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8

表示堆空间最大20M,最小20M,其中新生代10M(那么老年代就是20-10=10M),新生代中Eden区与一个Survivor区比例为8:1

新生代可总共可用空间=Eden区+1个Survivor区。另一个Survivor区是保留区域。

新生代在垃圾回收算法:复制算法。

垃圾回收的时候把Eden区和一个可用的Survivor区中存活的对象复制到另一个保留的Survivor区中,然后清理到Eden区和一个可用的Survivor区所有内容。

新生代内存分配:

1.对象优先在Eden分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

新生代的垃圾回收Minor GC:

1.Minor GC就是新生代的垃圾回收,它会把Eden和使用的那一个Survivor区的存活的对象都复制到保留的Survivor区中,然后清除到Eden和使用的那一个Survivor区。

如果保留的Survivor区装不下,则通过分配担保机制将其提前转移到老年代。

1.2.2 老年代 Tenured generation
老年代内存分配:

1.大对象直接进入老年代。比如很长的字符串以及数组。

在使用Serial或者ParNew垃圾回收器的情况下,可以设置参数-XX:PretenureSizeThreshold的值指定大于该值的对象直接在老年代分配。

2.长期存活的对象将进入老年代。

长期存活判断标准1:如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认是15岁),将会被晋升到老年代中。

对象晋升老年代的年龄阈值可以通过参数-XX:MaxTenuringThreshold设置。

长期存活判断标准2:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

老年代的垃圾回收:

1.Major GC

老年代的垃圾回收算法:

标记----清理或者标记----整理,由使用的垃圾回收器决定。

二、垃圾回收算法
2.1标记----清理算法 Mark-Sweep
首先标记出所有需要回收的对象,在标记完成后同一回收所有被标记的对象。这是最基础的收集算法。

缺点:效率低。标记清楚后会产生大量不连续的内存碎片。



2.2 复制算法 Copying
它将可用内存划分为大小相等的两块,每次使用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次清理掉



2.3 标记----整理算法 Mark-Compact
标记过程仍然与标记----清除算法一样,后续是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。



三、垃圾回收器
垃圾回收器就是对垃圾回收算法的实现。



各个垃圾回收器的特点:
http://nccloud.yytimes.com/uploads/answer/20230627/146c1a41cab11f789e85f22049cbac70.png

http://nccloud.yytimes.com/uploads/answer/20230627/43881b018832a8d9b2cbda4bcf9e78de.png

查看JVM参数:

1. 查看JVM进程的PID 或者通过 jps -l

2. jcmd pid VM.flags or jinfo -flags pid or jmap -heap pid

查看当前JVM使用哪种垃圾回收器:

java -XX:+PrintCommandLineFlags -version

我们生产上用的是:+UseParallelGC

JVM参数设置垃圾回收器:

-XX:+UseSerialGC,虚拟机运行在Client模式下的默认值,Serial+Serial Old。

-XX:+UseParNewGC,ParNew+Serial Old,在JDK1.8被废弃,在JDK1.7还可以使用。

-XX:+UseConcMarkSweepGC,ParNew+CMS+Serial Old。Serial Old收集器作为CMS收集器出现concurrent Mode failure失败后的后备收集器使用

-XX:+UseParallelGC,虚拟机运行在Server模式下的默认值,Parallel Scavenge+Serial Old(PS Mark Sweep)。

-XX:+UseParallelOldGC,Parallel Scavenge+Parallel Old。

-XX:+UseG1GC,G1+G1。

四、FullGC触发条件
FullGC是对整个堆(包括永生代及方法区)

良好的状态是:minor gc比较多 full gc 比较少 因为fullgc时间比较慢,而且会占用CPU的时间片。

不好的状态是:minor gc比较少 full gc 比较多 这样程序就一直卡在full gc上了。

1、System.gc()方法的调用

此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。

强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存。

可通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。

2、老年代代空间不足

老年代空间只有在新生代对象转入与创建为大对象、大数组时才会出现不足的现象,

当执行Full GC后空间仍然不足,则抛出如下错误:

Java.lang.OutOfMemoryError: Java heap space
为避免以上两种状况引起的Full GC,调优时应尽量做到:

让对象在Minor GC阶段被回收

让对象在新生代多存活一段时间

不要创建过大的对象及数组

3、永生区空间不足

未用CMS GC的情况下方法区(永久代)也会执行Full GC

如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:

java.lang.OutOfMemoryError: PermGen space
避免Perm Gen占满造成Full GC现象:

可增大Perm Gen空间

使用CMS GC

4、CMS GC时出现promotion failed和concurrent mode failure

对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能

会触发Full GC。

promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的。

concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。

对措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。

对于这种状况,可通过设置-XX: CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。

5、统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间

这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断:

如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。

例如程序第一次触发Minor GC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。

当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。

除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。

可通过在启动时通过- java -Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间

或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。

6、堆中分配很大的对象

所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。

为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数:

-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了。

-XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。

GC,即就是Java垃圾回收机制。目前主流的JVM(HotSpot)采用的是分代收集算法。与C++不同的是,Java采用的是类似于树形结构的可达性分析法来判断对象是否还存在引用。即:从gcroot开始,把所有可以搜索得到的对象标记为存活对象。

GC机制

要准确理解Java的垃圾回收机制,就要从:“什么时候”,“对什么东西”,“做了什么”三个方面来具体分析。

第一:“什么时候”即就是GC触发的条件。GC触发的条件有两种。(1)程序调用System.gc时可以触发;(2)系统自身来决定GC触发的时机。

系统判断GC触发的依据:根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。

第二:“对什么东西”笼统的认为是Java对象并没有错。但是准确来讲,GC操作的对象分为:通过可达性分析法无法搜索到的对象和可以搜索到的对象。对于搜索不到的方法进行标记。

第三:“做了什么”最浅显的理解为释放对象。但是从GC的底层机制可以看出,对于可以搜索到的对象进行复制操作,对于搜索不到的对象,调用finalize()方法进行释放。

具体过程:当GC线程启动时,会通过可达性分析法把Eden区和From Space区的存活对象复制到To Space区,然后把Eden Space和From Space区的对象释放掉。当GC轮训扫描To Space区一定次数后,把依然存活的对象复制到老年代,然后释放To Space区的对象。

对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。要完全回收一个对象,至少需要经过两次标记的过程。

第一次标记:对于一个没有其他引用的对象,筛选该对象是否有必要执行finalize()方法,如果没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;因为finalize方法只能被执行一次)。

第二次标记:如果被筛选判定位有必要执行,则会放入FQueue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象释放前被其他对象引用,则该对象会被移除FQueue队列。

GC过程中用到的回收算法:

通过上面的GC过程不难看出,Java堆中的年轻代和老年代采用了不同的回收算法。年轻代采用了复制法;而老年代采用了标记-整理法

具体各种回收算法的详解参考:http://www.cnblogs.com/dolphin0520/p/3783345.html

JVM内存空间图解

http://nccloud.yytimes.com/uploads/answer/20240920/5d94b97e409fb8207d895e0800eaa1b5.jpg

程序计数器:线程私有。是一块较小的内存,是当前线程所执行的字节码的行号指示器。是Java虚拟机规范中唯一没有规定OOM(OutOfMemoryError)的区域。

Java栈:线程私有。生命周期和线程相同。是Java方法执行的内存模型。执行每个方法都会创建一个栈帧,用于存储局部变量和操作数(对象引用)。局部变量所需要的内存空间大小在编译期间完成分配。所以栈帧的大小不会改变。存在两种异常情况:若线程请求深度大于栈的深度,抛StackOverflowError。若栈在动态扩展时无法请求足够内存,抛OOM。

Java堆:所有线程共享。虚拟机启动时创建。存放对象实力和数组。所占内存最大。分为新生代(Young区),老年代(Old区)。新生代分Eden区,Servior区。Servior区又分为From space区和To Space区。Eden区和Servior区的内存比为8:1。 当扩展内存大于可用内存,抛OOM。

方法区:所有线程共享。用于存储已被虚拟机加载的类信息、常量、静态变量等数据。又称为非堆(Non – Heap)。方法区又称“永久代”。GC很少在这个区域进行,但不代表不会回收。这个区域回收目标主要是针对常量池的回收和对类型的卸载。当内存申请大于实际可用内存,抛OOM。

本地方法栈:线程私有。与Java栈类似,但是不是为Java方法(字节码)服务,而是为本地非Java方法服务。也会抛StackOverflowError和OOM。

 Minor GC ,Full GC 触发条件
Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法去空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

要回复问题请先登录注册