从一次FULL GC说起 (一)

原创
2017/11/16 10:43
阅读数 366

      前几天突然收到一堆报警短信,吓得赶紧打开电脑查看日志,发现JVM不断做FULL GC,于是赶紧让运维重启服务器,接口监控提示一切正常。

      废话不多说,想要了解jvm的gc那得先说说jvm的内存结构:

  • 程序计数器

      记录每个线程执行到哪条字节码指令,解释器通过它来选取下一条执行的字节码指令,线程私有。

  • java栈

      以栈帧为单位,进行压栈和出栈,线程私有。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法的返回地址等信息。每一个方法从调用开始直至执行完成的过程,都对应的一个栈帧在虚拟机栈里入栈和出栈的过程。每次方法调用均会创建一个对应的Frame,方法执行完毕或者异常终止,Frame被销毁。一个方法A调用另一个方法B时,A的frame停止,新的frame被创建赋予B,执行完毕后,把计算结果传递给A,A继续执行。JVM Statck的大小可以是固定的,也可以是动态扩展的。如果线程需要一个比固定大小大的Stack,会发生StackOverflowError;如果动态扩展Stack时没有足够的内存或者系统没有足够的内存为新线程创建Stack,发生OutOfMemoryError。参数-Xss用来设置虚拟机栈的大小。

                                

  • 本地方法栈

       为native方法服务,线程私有,有可能和虚拟机栈合二为一。

       jvm内存中最大的一块,线程共享,用来分配对象实例,数组。GC回收的主要区域,根据GC回收机制又可以分为年轻代和老年代。-Xms,初始使用,默认物理内存 1/64。-Xmx,最大内存,默认物理内存 1/4。虚拟机会根据堆的空闲情况动态调整推大小,空余大于 70%,会减少到 -Xms,空余小于 40%,会增大到 -Xmx。所以服务器如果配置 -Xms = -Xmx,则可以避免堆自动扩展。

  • 方法区

       主要存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等),线程共享。

       关于这边有个有意思的东西:

Integer a=1;
Integer b=1;
Integer c=new Integer(1);
System.out.println(a==b);//true
System.out.println(a==c);//false

      而我们知道==判断基本类型的时候是判断值相等,判断类的时候判断地址相等。那为什么会出现这种情况呢?因为Byte,Short,Integer,Long,Character,Boolean这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据放进了各自的常量池,但是超出此范围仍然会去创建新的对象。 两种浮点数类型的包装类Float,Double并没有实现常量池技术。但是这个常量池并不是我们说的运行时常量池,而是各种包装类的缓存池。而String.intern是可以把值放进运行时常量池的,所以String.intern的不良使用可能会引发方法区的OOM。但在JDK1.8,将没有了方法区,字符串常量池,常量等回归java heap中,而类信息分到了叫做metaspace的native heap中。

      所以从上面的解释我们可以有一个jvm内存结果的图(JDK1.7前):

                     

       在对jvm 内存的了解后,我们知道Java 堆中存放着几乎所有的对象实例,但是资源是有限的,所以我们需要对对象进行回收。垃圾收集器对堆中的对象进行回收前,要先确定这些对象是否还有用,判定对象是否为垃圾对象有如下算法:

  • 引用计数算法

      给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1,当引用失效时,计数器值就减1,任何时刻计数器都为 0 的对象就是不可能再被使用的。引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的选择,但是Java语言并没有选择这种算法来进行垃圾回收,主要原因是它很难解决对象之间的相互循环引用问题。

  • 根搜索算法

      这种算法的基本思路是通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,就证明此对象是不可用的。在 Java 语言里,可作为 GC Roots 的兑现包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中的类静态属性引用的对象。
  3. 方法区中的常量引用的对象。
  4. 本地方法栈中 JNI(Native 方法)的引用对象。

      一般情况下,在垃圾回收期间,一个无法触及的对象会立即被销毁。不过,覆盖了finalize()方法的对象会被移动到一个队列里,一个独立的线程遍历这个队列,调用每一个对象的finalize()方法。在finalize()方法调用结束之后,这些对象才成为真正的垃圾,等待下一轮垃圾回收。

    

      

 

展开阅读全文
打赏
1
16 收藏
分享
加载中
陈二C博主

引用来自“kalo”的评论

JVM讲完没讲开篇你遇到的问题啊
这只是第一篇啊,我遇到的问题是CMS回收垃圾的时候concurrent mode failure,所以要在第二篇跟垃圾回收算法一起讲
2017/11/17 11:22
回复
举报
陈二C博主

引用来自“aiwen2015”的评论

上班时间写博客 你工作时间泄露了吧
嘘!你工作时间看博客也暴露了呀老铁
2017/11/17 11:17
回复
举报
JVM讲完没讲开篇你遇到的问题啊
2017/11/16 19:47
回复
举报
上班时间写博客 你工作时间泄露了吧
2017/11/16 17:39
回复
举报
更多评论
打赏
4 评论
16 收藏
1
分享
返回顶部
顶部