JVM之内存分配和回收机制

前言

本篇主要介绍JVM内存分配和回收策略,内容主要节选自《深入理解java虚拟机》。

一、内存分配策略

1. 堆内存模型

组成:

  • 新生代 默认占堆空间的三分之一,由于在新生代对象大多都是朝生夕死,则采用的是复制算法,在复制的期间会有频繁的Minor GC。
  • 老年代 默认占堆空间的三分之二,老年代对象大多都是长期存活的,则采用的是标记算法,老年代的Major GC开销非常大。
  • Eden 新生代分为2个区 Eden和Survivor区 其中Eden区默认占新生代十分之八的空间,对象优先进入Eden区。
  • Survivor 当Eden区内存满了之后会进行一次Minor GC存活对象会进入Survivor区,默认占新生代十分之二空间, 其中它又均分为From区和To区,在Survivor区的对象每熬过一次从From区到TO区则年龄+1。

大多情况下,对象在新生代Eden区中分配。当Eden没有足够的空间分配对象时虚拟机会发起一次Minor GC。

2.2 大对象直接到老年代

大对象即需要大量连续内存空间的对象(例如很长的字符串及数组)。虚拟机提供了一个-XX:PretenureSizeThreshoId参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在Eden区及两个区之间发生大量的内存复制。注意PretenureSizeThreshoId参数只对Serial和ParNew两款收集器有效。

2.3 动态年龄判断

为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进人老年代,无须等到MaxTenuringThreshoId中要求的年龄。

2.4 内存担保机制

在发生Minor GC之前,虚拟机会先检查Survivor空间是否够用,如果够用则直接进行Minor GC。否则进行检查老年代最大连续可用空间是否大于新生代的总和,假如大于,那么这个时候发生Minor GC是安全的。假如不大于,那么需要判断HandlePromotionFailure设置是否允许担保失败。假如允许,则继续判定老年代最大可用的连续空间是否大于平均晋升到老年代对象的平均值,如果大于,这个时候可以发生Minor GC ,如果小于或者设置HandlePromotionFailure不允许担保失败,则需要做一次Full GC。通常会把HandlePromotionFailure开关打开,以减少Full GC。

2.5 长期存活对象

虚拟机给每个对象定义了一个对象年龄(Age)计数器(存在于对象头中)。如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survwor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshoId设置。

二、对象存活

判断对象存活一般有两种方式: 引用计数算法和可达性分析算法。

1.引用计数算法

原理:

引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。

优点:

实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

缺点:

它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。每次复制都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。 2.可达性分析算法

定义:相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

原理:

可达性分析算法是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)。如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

GC ROOT 对象:

虚拟机栈中引用的对象;

比如:各个线程被调用的方法中使用到的参数、局部变量等。本地方法栈内 JNI(通常说的本地方法)引用的对象;方法区中类静态属性引用的对象;
比如:Java类的引用类型静态变量方法区中常量引用的对象;
比如:字符串常量池(string Table)里的引用所有被同步锁 synchronized 持有的对象;Java虚拟机内部的引用。
基本数据类型对应的 Class 对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器。 3.再谈引用 强引用:强引用在代码中普遍存在,例如Object obj=new Object() 这类的引用。只要强引用存在,垃圾回收器永远不会回收掉被引用的对象。软引用:软引用来描述一些还有用但并非必须的对象,在系统要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收,如果第二次回收还没有足够的内存,才会抛出内存溢出异常。弱引用:弱引用也是用来描述必须对象的,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器工作时,无论内存是否足够,都回收掉只被弱引用关联的对象。虚引用:它是最弱的一种引用关系。它无法通过虚引用来取得一个对象实例。唯一目的就是能在这个对象被回收之前会收到一个系统通知。

三、内存回收

1.堆内存回收

是JVM所管理内存最大的一块,也是gc回收的主要区域。

1.1 哪些对象能回收?

堆内存中对象存活是使用可达性分析算法来判断,其中非存活对象由GC回收掉。这个就是虚拟机需要回收堆的对象。

1.2 如何回收?

Minor GC:新生代收集,目标只是新生代的垃圾收集;

Major GC:老年代收集,目标是老年代的垃圾收集(具体说只有CMS会有单独收集老年代的行为);

Full GC:收集整个java堆和方法区的垃圾收集。这里补充说明一下虽然网上很多说什么Full GC就是Major GC,在这里我要重申一下并不是,具体看书上描述如下:

Mixed GC:收集整个新生代以及部分老年代的垃圾收集,仅G1支持。(类似于Full GC)

1.3 什么时候回收?

Minor GC触发条件:

Eden区域满了,会触发Minor GC;新生对象需要分配到新生代的Eden,当Eden区的内存不够时需要进行MinorGC。

Major GC触发条件:

老年代区域设置的阈值空间满了,会触发Major GC;新对象需要分配到老年代,此时老年代设置阈值可用空间不足时触发Major GC。

Full GC触发条件:

内存担保机制 ,Survivor空间不足时,判断是否允许担保失败,如果不允许则进行Full GC。如果允许,并且每次晋升到老年代的对象平均大小>老年代最大可用连续内存空间,也会进行Full GC;MinorGC后存活的对象超过了老年代剩余空间;方法区内存不足时;程序中调用了System.gc()方法,可用通过-XX:+ DisableExplicitGC来禁止调用System.gc;CMS GC异常,CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,会触发Full GC。 2.方法区回收

方法区主要回收废弃的常量池和不再使用类型,但这个2类对象存活的判断还不一样。

2.1 常量池

同堆的对象存活类似-可达性分析法,具体请参考之前的可达性分析法。

2.1 类型数据 该类索引的实例都已经被回收;加载该类的类加载器已经被回收;该类对应的java.lang.class对象没有任何地方被引用。

以上都是我简单总结,以下是书上关于方法区回收描述的内容:

其实从书上就可以看出来,关于方法区OOM问题大都是在程序中是有大量使用反射、动态代理、CGLIB等框架,如果在实际开发中遇到关于可以从以上几个维度来定位问题。

总结

本篇所有理论知识都是摘抄于《深入理解java虚拟机》,有部分是自己简单总结,JVM内存分配和回收是我们在分析JVM调优和相关问题的基石,建议看完我本篇的去多看几遍《深入理解java虚拟机》。

(0)

相关推荐

  • 详解CLR的内存分配和回收机制

    一.CLR CLR:即公共语言运行时(Common Language Runtime),是中间语言(IL)的运行时环境,负责将编译生成的MSIL编译成计算机可以识别的机器码,负责资源管理(内存分配和垃圾回收等). 可能有人会提问:为什么不直接编译成机器码,而要先编译成IL,然后在编译成机器码呢? 原因是:计算机的操作系统不同(分为32位和64位),接受的计算机指令也是不同的,在不同的操作系统中就要进行不同的编译,写出的代码在不同的操作系统中要进行不同的修改.中间增加了IL层,不管是什么操作系统,

  • 深入了解java内存分配和回收策略

    一.导论 java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配.通俗的讲,对象的内存分配就是在堆上的分配,对象主要分配在新生代的Eden上(关于对象在内存上的分代在垃圾回收中会补上,想了解的也可以参考<深入理解java虚拟机>),如果启动了本地线程分配缓冲,讲按线程优先在TLAB上分配.少数情况下也是直接在老年代中分配. 二.经典的分配策略 1.对象优先在Eden上分配 一般情况下对

  • 解析Java内存分配和回收策略以及MinorGC、MajorGC、FullGC

    目录 对象内存分配与回收策略 对象何时进入新生代.老年代 三种GC介绍 MinorGC Major GC/Full GC: 图示GC过程 对象内存分配与回收策略 对象的内存分配,往大方向讲,就是在堆上分配[但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中. 对象优先分配在Eden区,当Eden区可用空间不够时会进行MinorGC 大对象直接进入老年代:大对

  • Java虚拟机内存分配与回收策略问题精细解读

    本文参考于<深入理解Java虚拟机> 内存分配与回收策略 Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存. 1. 综述 对象的内存分配,从概念上讲,应该都是在堆上分配(而实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配).在经典分代的设计下,新生对象通常会分配在新生代中,少数情况下(例如对象大小超过一定阈值)也可能会直接分配在老年代.对象分配的规则并不是固定的,<Java虚拟机规范>并未规定新对象的创

  • JavaScript对内存分配及管理机制详细解析

    你可能听说过JAVA..NET.PHP这些语言有垃圾回收的内存管理机制,但是很少会听到JavaScript也有自己的内存管理机制,JavaScript同样有着类似的垃圾回收功能.本文主要讲述了JavaScript的垃圾回收原理和具体的过程. 简介在底层语言中,比如C,有专门的内存管理机制,比如malloc() 和 free().而Javascript是有垃圾回收(garbage collection)机制的,也就是说JS解释器会自动分配和回收内存.这样就有人觉得,我用的是高级语言,就不用关心内存

  • Java GC 机制与内存分配策略详解

    Java GC 机制与内存分配策略详解 收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现 自动内存管理解决的是:给对象分配内存 以及 回收分配给对象的内存 为什么我们要了解学习 GC 与内存分配呢? 在 JVM 自动内存管理机制的帮助下,不再需要为每一个new操作写配对的delete/free代码.但出现内存泄漏和溢出的问题时,如果不了解虚拟机是怎样使用内存的,那么排查错误将是一项非常艰难的工作. GC(垃圾收集器)在对堆进行回收前,会先确定哪些对象"存活",哪些已经&quo

  • 简单介绍Java垃圾回收机制

    Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Java深受大家欢迎的众多特性之一,能够帮助程序员更好地编写Java程序. 这篇教程是系列第一部分.首先会解释基本的术语,比如JDK.JVM.JRE和HotSpotVM.接着会介绍JVM结构和Java堆内存结构.理解这些基础对于理解后面的垃圾回收知识很重要. Java关键术语 JavaAPI:一系列帮助开发者创建Java应用程序的封装好的库. Java开发工具包(JDK):一

  • 深入理解JVM自动内存管理

    目录 一.前言 1.1 计算机==>操作系统==>JVM 1.1.1 虚拟与实体(对上图的结构层次分析) 1.1.2 Java程序执行(对上图的箭头流程分析) 二.JVM内存空间与参数设置 2.1 运行时数据区 2.2 关于StackOverflowError和OutOfMemoryError 2.2.1 StackOverflowError 2.2.2 OutOfMemoryError 2.3 JVM堆内存和非堆内存 2.3.1 堆内存和非堆内存 2.3.2 JVM堆内部构型(新生代和老年代

  • 关于JVM翻越内存管理的墙

    目录 JVM运行时数据区域 程序计数器 Java虚拟机栈 栈桢 本地方法栈 Java堆 分配缓冲区TLAB(Thread Local Allocation Buffer) Java堆的大小设定 方法区 运行时常量池 小结 JVM垃圾回收机制 判断对象存活 引用计数算法 可达性分析算法 几种引用方式 垃圾回收算法 标记清除算法 标记复制算法 标记整理法 分代收集算法 内存回收策略 1.新生代的分配和回收 2.大对象直接进入老年代 3.长期存活的对象将进入老年代 参考 对于Java程序员来说,在虚拟

  • Java 内存分配深入理解

    Java 内存分配深入理解 本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java.这类文章网上有很多,但大多比较零碎.本文从认知过程角度出发,将带给读者一个系统的介绍. 进入正题前首先要知道的是Java程序运行在JVM(Java  Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性.所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是

随机推荐

其他