JVM的内存模型

 JVM虚拟机的组成部分

JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、 Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

  • Class loader(类装载):根据给定的全限定名类名(如: java.lang.Object)来装载class文件到Runtime data area中的method area。
  • Execution engine(执行引擎):执行classes中的指令
  • Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口,可以看到jdk中有很多native修饰的方法,这些就是和操作系统自带的接口交互的方法。
  • Runtime data area(运行时数据区):一般说JVM的内存指的就是这一块

如图可看到,一般首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader) 再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

JVM内存模型

而运行时数据区分为:堆,线程栈,本地方法栈,方法区(元空间),程序计数器

  • 程序计数器:记录线程执行的的位置
  • 线程栈:由栈帧组成,栈帧存储着方法的相关信息:局部变量表,操作数栈,动态链接,方法出口等。
  • 本地方法栈:与虚拟机栈的作用是一样的,只不过线程栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的,执行引擎执行时加载
  • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
  • 堆:被所有线程共享 的,几乎所有的对象实例都在这里分配内存

当输入指令运行某个class文件时,首先会由类装载子系统进行加载,将所有类结构和方法变量放入运行时数据区,初始化之后,将程序的执行交给执行引擎。程序运行时,开启线程时将会创建一个线程栈。

堆内存

首先要明白所谓年轻代和老年代指的是堆内存的结构

默认的年轻代和老年代比值为1:2,当然是可以调整的

年轻代分为:eden区,survivor区(s1和s2),他们之间内存分配比值为8:1:1

  • Young 年轻区(代)

Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移 动到Tenured区间。 

  • Tenured 年老区

Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数(Survivor区每转移一次年龄加1,当值为15后会转移到老年代)以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。  

堆的物理地址分配对象是不连续的,因此性能慢些,也会造成内存碎片,而且内存不清理也会有用完的时候,所以有垃圾回收机制

垃圾回收机制

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。在执行垃圾回收线程时会出现STW(stop-the-world,当执行垃圾回收时所有的线程将暂停执行)

定位垃圾的方法

  • 引用计数器法(Reference Counting)

当某个对象每被引用一次,则会标记加1,当没有引用时则为0,此时则为垃圾。但是此算法缺陷是当某几个对象相互引用后,但是又没有其他对象去引用这几个对象,将会找不出这几个垃圾来。

  • 根可达算法(Root Searching)

此算法则是从根开始找相关的引用对象,找到了的若找到了则不是垃圾,反之亦然。此算法弥补了引用计数器法的缺陷。Java中就是采用的此方法,而Java中所谓的根则包括:线程栈,本地方法栈,常量池,静态方法区,class对象等

垃圾回收算法

  • 标记清除法(Mark-Sweep)

找到了垃圾则直接标记为垃圾清除,但是此算法的缺陷是会产生大量的内存碎片,如果此时需要使用连续内存(例如数组)则会受影响。

  • 复制算法(Coping)

此算法将内存分为两半,但是只是用一半,当垃圾回收器进行垃圾回收时,将当前非垃圾的数据复制到另一半去排列好,并清除当前这一半内存区域。缺点是浪费内存,因为只有一半的内存在被使用。

  • 标记压缩法(Mark-Compact)

和标记清除法有点类似,但是在清除垃圾时,会将数据进行排列,内存进行整理,就不会有内存碎片。但是很明显由于多了整理内存碎片的步骤,所以此算法效率较低。

  • 分代收集算法

当前商业虚拟机都采用分代收集的垃圾收集算法。分代收集算法,顾名思义是根 据对象的存活周期将内存划分为几块。一般包括年轻代、老年代和永久代(1.8之后没有永久代)

每一种垃圾回收算法都有其优缺点,于是将几种算法进行了综合形成了不同的垃圾回收器。

JAVA垃圾回收器(GC)

  1. Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,由于是单线程STW,如果内存很大,清理时间长,其他线程都会暂停很长时间,所以将会很影响性能;
  2. ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现
  3. Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景
  4. Serial Old收集器 (标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本
  5. Parallel Old收集器 (标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本
  6. CMS(Concurrent Mark Sweep)收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最 短GC回收停顿时间。
  7. G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。在JDK9中默认的就是G1垃圾回收器
  8. ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
  9. Shenandoah 算法:ColoredPointers + WriteBarrier

对于前6种垃圾回收器,会区分年轻代和老年代,他们的使用也有对应的关系(如图),而后三种的使用是不会严格区分年轻代老年代了。

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收

CMS垃圾回收过程

  1. 初始化标记(CMS-initial-mark) ,标记root,会导致stw,但是时间会很短,只需要找到root;
  2. 并发标记(CMS-concurrent-mark),这里的并发指垃圾回收器线程与用户线程同时运行,此时垃圾回收线程标记为垃圾,但用户线程可能再次使用此对象而导致不是垃圾,从而导致标记出错;
  3. 预清理(CMS-concurrent-preclean),与用户线程同时运行;
  4. 重新标记(CMS-remark) ,将并发标记中标记出错的重新标记,所以此时为导致stw;
  5. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
  6. 调整堆大小,设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片;
  7. 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;

但CMS最重要的是初始标记,并发标记,重新标记,并发清除

 CMS的缺点:

1.产生内存碎片:当老年代进行FullGC时,使用的标记清除算法,当年轻代有数据过来时,找不到空间会使用serial方式使用标记压缩算法清除空间,如果内存空间较大会极其影响性能(内存达到32G必定出问题)

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩

2.浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然会有新垃圾产生,这部分垃圾是在标记过程之后,所以CMS无法在当前收集中处理掉他们,只好留待下一次GC清理掉,这一部分垃圾称为浮动垃圾。在jdk1.5默认设置下,CMS收集器当老年代使用了68%的空间就会被激活,可以通过-XX:CMSInitialOccupancyFraction的值来提高触发百分比,在jdk1.6中CMS启动阈值提升到了92%,要是CMS运行期间预留的内存无法满足程序的需要,就会出现”Concurrent Mode Failure“,然后降级临时启用Serial Old收集器进行老年代的垃圾收集,这样停顿时间就很长了,所以-XX:CMSInitialOccupancyFraction设置太高容易导致大量”Concurrent Mode Failure“。

G1垃圾回收器

G1垃圾收集器也是以关注延迟为目标、服务器端应用的垃圾收集器,被HotSpot团队寄予取代CMS的使命,也是一个非常具有调优潜力的垃圾收集器。虽然G1也有类似CMS的收集动作:初始标记、并发标记、重新标记、清除、转移回收,并且也以一个串行收集器做担保机制,但单纯地以类似前三种的过程描述显得并不是很妥当。

  • G1的设计原则是"首先收集尽可能多的垃圾(Garbage - First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部- 采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
  • G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
  • G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
  • G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次- 收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合- 收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿

G1还有一个及其重要的特性:软实时(soft real-time)。所谓的实时垃圾回收,是指在要求的时间内完成垃圾回收。“软实时”则是指,用户可以指定垃圾回收时间的限时,G1会努力在这个时限内完成垃圾回收,但是G1并不担保每次都能在这个时限内完成垃圾回收。通过设定一个合理的目标,可以让达到90%以上的垃圾回收时间都在这个时限内。

Minor GC和FULL GC

  • Minor GC:是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所 有 Minor GC 非常频繁,一般回收速度也非常快
  • Full GC/ Major GC :是指发生在老年代的 GC,出现了 Full GC 通常会伴 随至少一次 Minor GC。Full GC 的速度通常会比 Minor GC慢10倍以上

而JVM调优就是要防止频繁的FullGC,JVM调优可以参考JVM调优的运行参数的使用和jvm监控_u010689849的博客-CSDN博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值