JVM 垃圾回收 内存结构

一、JVM内存结构


image
程序计数器(Program Counter Register)

    是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
    在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。
    因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

Java虚拟机栈(Java Virtual Machine Stacks)

也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。
每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈(Native Method Stacks)

与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。

Java堆(Java Heap)

是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

直接内存(Direct Memory)

    并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,
    但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。

二、垃圾收集


垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象有哪些还“存活”着:

    1.引用计数算法
2.根搜索算法

 在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

·强引用就是指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

·软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。

·弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。

·虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

回收方法区

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集的“性价比”一般比较低:
在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。

垃圾收集算法

1.标记-清除算法
2.复制算法
3.标记-整理算法
4.分代收集算法

垃圾收集器
image
1.Serial收集器

    这个收集器是一个单线程的收集器,必须暂停其他所有的工作线程(Sun将这件事情称之为“Stop The World”),直到它收集结束。

2.ParNew收集器

    ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,
其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,
实现上这两种收集器也共用了相当多的代码。

·并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

·并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上。

3.Parallel Scavenge收集器

Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数及直接设置吞吐量大小的-XX:GCTimeRatio参数。

MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过设定值。不过大家不要异想天开地认为如果把这个参数的值设置得稍小一点就能使得系统的垃圾收集速度变得更快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:系统把新生代调小一些,收集300MB新生代肯定比收集500MB快吧,这也直接导致垃圾收集发生得更频繁一些,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

GCTimeRatio参数的值应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(即1/(1+99))的垃圾收集时间。

由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称为“吞吐量优先”收集器。除上述两个参数之外,Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得关注。

4.Serial Old

是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是被Client模式下的虚拟机使用。
如果在Server模式下,它主要还有两大用途:
一个是在JDK 1.5及之前的版本中与Parallel Scavenge收集器搭配使用,
另外一个就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure的时候使用。

5.Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

6.CMS(Concurrent Mark Sweep)收集器

是一种以获取最短回收停顿时间为目标的收集器。
目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
CMS收集器就非常符合这类应用的需求。

从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:

·初始标记(CMS initial mark)

·并发标记(CMS concurrent mark)

·重新标记(CMS remark)

·并发清除(CMS concurrent sweep)

CMS是一款优秀的收集器,它的最主要优点在名字上已经体现出来了:并发收集、低停顿,
Sun的一些官方文档里面也称之为并发低停顿收集器(Concurrent Low Pause Collector)。
但是CMS还远达不到完美的程度,它有以下三个显著的缺点:
    CMS收集器对CPU资源非常敏感。
    CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
    CMS是一款基于“标记-清除”算法实现的收集器,可能想到这意味着收集结束时会产生大量空间碎片。

7.G1(Garbage First)收集器

是当前收集器技术发展的最前沿成果。
G1收集器是垃圾收集器理论进一步发展的产物,它与前面的CMS收集器相比有两个显著的改进:
一是G1收集器是基于“标记-整理”算法实现的收集器,也就是说它不会产生空间碎片,这对于长时间运行的应用系统来说非常重要。
二是它可以非常精确地控制停顿,既能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力地避免全区域的垃圾收集,
之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),
并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,
优先回收垃圾最多的区域(这就是Garbage First名称的来由)。
区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。

内存分配与回收策略

  • 对象优先在Eden分配
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
  • 动态对象年龄判定
  • 空间分配担保

虚拟机性能监控与故障处理工具

  • jps:虚拟机进程状况工具
  • jstat:虚拟机统计信息监视工具
  • jinfo:Java配置信息工具
  • jmap:Java内存映像工具
  • jhat:虚拟机堆转储快照分析工具
  • jstack:Java堆栈跟踪工具
  • JDK的可视化工具
    • JConsole:Java监视与管理控制台
    • VisualVM:多合一故障处理工具
    • BTrace动态日志跟踪

猜你喜欢

转载自blog.csdn.net/zjltju1203/article/details/89211661