您的位置 首页 java

Java内存模型,垃圾回收,看这一篇就够了

JVM内存模型

Java内存模型,垃圾回收,看这一篇就够了

Jdk1.7之前

如上图所示,在Java1.8之前,JVM内存模型主要分两块,绿色部分的堆和方法区是线程共有的,粉色部分的虚拟机栈、本地方法栈和程序计数器则是线程私有的。1.8之后,一个显著的区别就死将“方法区”移出来变成“元数据区”,并且它不再由JVM来维护,改为OS的直接内存(也叫堆外内存),这样的好处就是使得其大小不再受JVM限制。

1. 堆(Heap)

堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。

堆内存分为两个部分:新生代和老生代(大小比例1:2)。新生代由Eden(伊甸区)和Survivor0,Survivor1(幸存区)组成,三者的比例是8:1:1。当java类新建对象时,首先会存在于Eden区,当空间不够时,将依次进入s0、s1区,当整个新生代空间不够,则会启动Minor GC,将对象移入老生代。

Java内存模型,垃圾回收,看这一篇就够了

堆的大小通过-Xms(最小值)和-Xmx(最大值)参数设置,前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列。为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。

2. 方法区(Method Area)

方法区也称”永久代“,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数指定。

它是一片连续的堆空间,永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

3.虚拟机栈(JVM Stack)

每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作栈、方法出口等信息。栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区被组织为以一个字长为单位、从0开始计数的数组;和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的,可以看作为临时数据的存储区域。

除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。

4.本地方法栈(Native Stack)

与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。(栈的空间大小远远小于堆)

5.程序计数器(PC Register)

是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

6.直接内存

直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小.

JVM垃圾回收算法

1.标记清除

原理:

  • 从根节点进行扫描,标记出所有的存活对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)
    • 适用于老生代:存活对象较多的情况下比较高效

缺点:

  • 会产生大量的空间碎片,因为回收后的空间是不连续的,这样给大对象分配内存的时候可能会提前触发full gc。

2.复制算法

原理:

  • 从根节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存上去,之后将原来的那一块儿内存全部回收掉。
    • 适用于新生代:存活对象较少的情况下比较高效

缺点:

  • 需要一块儿空的内存空间
  • 需要复制移动对象

3.标记整理

原理:

  • 执行一次标志清除操作,之后将所有的存活对象左移到一起。
  • 用于年老代(即旧生代)

缺点:

  • 需要移动对象,若对象非常多而且标记回收后的内存非常不完整,可能移动这个动作也会耗费一定时间

优点:

  • 不会产生内存碎片

4.增量算法

基本思想是,让垃圾收集线程和应用程序线程交替执行,每次只收集一小片区域的内存空间,接着切换到应用程序线程。使用这种方式,能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

垃圾回收器

1.Serial收集器

  • 串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
  • 参数控制:-XX:+UseSerialGC 串行收集器

2.ParNew收集器

  • ParNew收集器是Serial收集器的多线程实现,依然会stop the world,只是相比较而言它会运行多条进程进行垃圾回收。
  • -XX:+UseParNewGC
  • -XX:ParallelGCThread 线程数。

3.Parallel Scavenge收集器

  • Parallel收集器 收集器类似ParNew收集器,它更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;
  • -XX:+UseParallelGC
  • -XX:MaxGCPauseMillis 最大GC停顿时间, -XX:GCTimeRatio 。

4.CMS收集器

  • CMS(Concurrent Mark Swep)收集器是一种获取最短回收停顿时间为目标的收集器,这使得它很适合用于和用户交互的业务。它是基于“标记-清除”算法实现的。整个过程分为四个步骤:1、初始标记(initial mark) 2、并发标记(concurrent mark) 3、重新标记(remark) 4、并发清除(concurrent sweep)
  • 初始标记和重新标记还是会停顿,但是在耗费时间更长的并发标记和并发清除两个阶段都可以和用户进程同时工作。
  • 优点:并发收集、低停顿
  • 缺点:产生大量空间碎片、并发阶段会降低吞吐量
  • -XX:+UseConcMarkSweepGC
  • -XX:CMSInitiatingOccupancyFraction 老生代启动fullGC的阈值
  • -XX:+UseCMSInitiatingOccupancyOnly 一直使用上面的值

5.G1收集器

  • 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
  • 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
  • 使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合
  • G1将新生代,老年代的物理空间划分取消了。

虽然G1看起来有很多优点,实际上CMS还是主流。

与GC相关的常用参数

除了上面提及的一些参数,下面补充一些和GC相关的常用参数:

  • -Xmx: 设置堆内存的最大值。
  • -Xms: 设置堆内存的初始值。
  • -Xmn: 设置新生代的大小。
  • -Xss: 设置栈的大小。
  • -PretenureSizeThreshold: 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。
  • -MaxTenuringThrehold: 晋升到老年代的对象年龄。每个对象在经过一次Minor GC之后,年龄就会加1,当超过这个参数值时就进入老年代。
  • -UseAdaptiveSizePolicy: 在这种模式下,新生代的大小、eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完成调优工作。
  • -SurvivorRattio: 新生代Eden区域与Survivor区域的容量比值,默认为8,代表Eden: Suvivor= 8: 1。
  • -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。
  • -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。
  • -XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

文章来源:智云一二三科技

文章标题:Java内存模型,垃圾回收,看这一篇就够了

文章地址:https://www.zhihuclub.com/183685.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图