JVM运行内存分配和回收

本文来自网易云社区

作者:吕宗胜

Java语言与C语言相比,最大的特点是编程人员无需过多的关心Java的内存分配和回收,因为所有这一切,Java的虚拟机都帮我们实现了。JVM的内存管理,大大降低了开发人员对内存管理的要求,也不容易出现C语言中的内存泄漏和溢出。但一旦应用内存发生问题,也会导致程序员难以定位。所以对于Java程序员来说认识和了解JVM的内存分配和回收对于代码的编写和应用的优化都有非常重要的意思。

1. JVM内存模型

Java的JVM的类型是非常多样的,不同的JVM对于内存的分配和回收机制都不尽相同。我们这里仅仅介绍的是最为流行的JVM,HotSpot VM,它是目前使用范围最广的Java虚拟机。但是JVM的更新速度也非常快,不同的版本之间也可能会存在一些区别,但总体来说,其构架还是相对稳定的。

说到内存管理,我们首先要了解的就是Java运行时的数据区域,包括线程私有数据和共享数据的分配等等方面。根据《Java虚拟机》中的描述,其运行时数据区域为:

从上图可以看出,运行时的数据区域主要分成了5个部分:方法区、堆、虚拟机栈、本地方法栈和程序计数器。下面我们分别来介绍这5个部分。

1. 方法区

方法区中存储的数据被各个线程所共享,用于存储被虚拟机加载的类信息、常亮、静态变量、编译后的代码等数据。

2. 堆

堆区域存储的所有数据被各个线程共享,也是我们程序中最为关心的内存区域。该区域的目的就是存放对象实例,所以我们程序中的几乎所有对象都是存储在这块区域的。同时,该区域也是JVM进行内存管理和回收的主要区域。

3. 虚拟机栈

虚拟机栈是除堆之外最重要的一块内存区域,虚拟机栈中的数据是线程私有的。虚拟机栈是Java方法执行的内存模型,在每个方法执行时都会创建栈帧,用于存储局部变量表,操作数栈等。

4. 本地方法栈

它与虚拟机栈的作用是十分相似的,而它们的区别是虚拟机栈是用于执行Java方法时的数据结构,而本地方法栈是Java使用的Native方法服务。

5. 程序计数器

程序计数器是非常小的一块内存,每个线程都有一个独立的程序计数器,通过程序计数器,我们可以知道当前线程的执行的字节码序号。

上面简单的了解了一下JVM运行时的数据区域和每个区域的基本功能,而在实际的使用过程中,我们最为关心的就是堆区域和虚拟机栈中的局部变量表。而对于线程私有的虚拟机栈而言,数据内存随着线程的消亡而回收,而堆数据的回收则成为JVM内存管理和回收的重点。

2. 内存管理的分代机制

JVM中,目前使用的内配管理是分代方式,即把内存分成新生代、老生代和永久代。这里我们讲的分代管理机制是针对线程共享的内存区域,主要是堆,也包括方法区。

JAVA分代机制的好处是可以根据Java的实际对象创建和销毁时机,在不同的生代中可以采用不同的垃圾回收策略,已提高垃圾回收的效率。在Java中,几乎所有对象的实例都分配与新生代,而大部分对象的存活时间都不长,新生代中的对象回收会比较频繁。而老年代中的存放是那些存活时间较长,或者对象过大导致无法在新生代中分配的对象。而永久代比较特殊,它一般是指内存区域中的方法区,HotSpot在实现方法区时作为永久代来处理,避免了额外来管理方法区。这块区域的内存回收我们一般不做考虑,因为效果不会很明显,而且回收的条件也非常苛刻。

3.垃圾清除算法

针对不同的分代以及其特性,不同分代使用的垃圾回收策略也是不一样的。

3.1标记-清除算法

标记清除算法其实非常简单,它是先标记那些已经死亡的对象,然后对这些死亡的对象进行清理。但是它的一个很大的不足在于直接清理会产生非常多得内存碎片,导致后续分配内存会因为碎片的问题而没有连续的大空间满足分配,从而触发下一次的垃圾回收。可想而知,该垃圾清除策略效率和空间上都不会是最优的。

3.2 复制算法

复制算法,其实本质上跟标记-清除算法没有区别,不过它解决了内存碎片化的问题,同时也解决了两次扫描的问题。它的实现方式是在内存分配时先预留一部分内存,当内存需要回收的时候,它会进行扫描,把没有过期的内存数据复制到预留的内存,而直接清理原先分配的内存,把原先分配的内存作为预留内存。这种方法的好处就是效率很高,缺点也非常明显,那就是要浪费一部分内存作为预留内存,而如果为了保证数据100%的不丢失,原则上我们需要预留所有可分配内存的一半,造成内存的大面积浪费。

在新生代中,JVM采用了复制算法,因为新生代中的对象基本都是朝生夕死的,所以每次垃圾回收效果会比较明显,我们也称之为MinorGC。这里新生代划分成3块区域,Eden区,From Survivor区和To Survivor区。两块Survivor互为备份,垃圾回收时,对象会集中复制到空闲的Survivor区中去。为了提高内存的利用率,这个Eden区会占用较大的比例,默认比例是8:1。这样新生代只有10%的内存被浪费掉,但是毕竟很是有大量对象不能被回收而导致Survivor区空间不足的问题。这里就涉及到分配担保问题,当Survivor区不够的时候,对象会直接进入老年代。

3.3 标记-整理算法

复制算法除了空间的浪费外,还有一个问题就是如果对象是长期存活的,将会导致内存回收的效率降低,因为复制的内存将会变大。所以复制算法比较适合那些对象存活期较短的内存区域回收。所以在复制和标记-清除算法的基础上,提出了标记-整理算法。标记-整理算法也是先对对象进行标记,而后该算法将存活的对象往内存的一个方向移动,最终的内存将是占用的内存和空闲的内存有明显的分界,它主要是解决了内存碎片化的问题。

与新生代的朝生夕死相比,老生代的对象存活时间会比较长,所以采用了标记-整理算法。如果发生了老生代的垃圾回收,我们称之为FullGC。老生代的回收效率较低,会导致系统暂停较长的时间,所以我们要尽量减少FullGC的发生。

4. 分配回收策略

上面我们看到了JVM分代的垃圾回收算法,下面我们来看看JVM在内存分配和回收中的一些最常见的几个点。

4.1 对象优化分配在Eden区

Java的对象优先分配在Eden区中,当Eden区中没有足够的内存分配时,JVM会进行一次MinorGC。所以JVM中MinorGC会是比较频繁的垃圾回收动作,一般回收速度也比较快。对象分配在Eden区也不是绝对的,有一种例外是大对象会直接进入老年代。这里的大对象是指需要连续内存空间的Java对象,比如说很长的字符串和数组等。大对象直接进入老年代非常不适合垃圾回收策略,特别是这些大对象也是那些朝生夕死的对象,这会造成比较频繁的FullGC,导致系统性能降低。

4.2 长期存活的对象进行老年代

每一个对象都有一个对象年龄,对象在新生代中每经过一次垃圾回收,对象年龄增长1,当对象年龄超过某个阈值时,该对象会进入老年代。所以这里就有一个问题,如果我们在非常频繁的进行垃圾回收时,对象的对象年龄就会快速增长,一个对象会非常容易的进行老年代,造成FullGC的次数增长。

5. 对象死亡的判断算法

上面我们介绍了垃圾回收,却一直没有介绍JVM中使用的判断对象死亡的算法。最简单的对象判断的算法是采用计数法。当对象被引用时,计数加1,当一个对象的引用计数为0时,表示该对象已经死亡,可以进行回收。计数的方法虽然简单,易实现,但是却不能解决相互引用的问题,比如说对象A引用B,B也引用A,而A和B不再被其他对象引用,这种情况下,如果AB对象是可以被回收的,但是计数确不为0。

目前,通用的判断对象死亡的方法是可达性分析算法。可达性分析是指从对象起点开始,如果该对象可以被引用到,则该对象是活着的,否则,该对象则死亡了。那么该算法中最基本的对象起点是哪些呢?这些对象是指虚拟机栈中引用的对象、方法区中引用的对象和本地方法栈中引用的对象。

本文来自网易云社区,经作者吕宗胜授权发布

相关文章:
【推荐】 OBS源码编译开发

原文地址:https://www.cnblogs.com/163yun/p/9625582.html

时间: 2024-08-04 20:56:13

JVM运行内存分配和回收的相关文章

最简单例子图解JVM内存分配和回收

一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分为Eden区(圣经中的伊甸园).和两个Survivor区.新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到年老区. 简单讲,就是生命期短的对象放在一起,将少数生命期长的对象放在一起,分别采用不同的回收

JVM内存分配与回收分析

java虚拟机的自动化内存可以归结为自动化解决了两个问题,一个是内存分配,一个是内存回收.了解虚拟机的分配与回收机制,能让我们对项目的把控更加有力,尤其是对性能调优时, 各个参数的设置可能会有意想不到的效果.本文结合事例分析各种场景的回收. 堆内存区域(不包括永久代)种类:1,eden space(属于新生代-new generation) 2,survivor space(属于新生代-new generation) 3,tenured generation(年老代) 首先解释下各参数的含义.我

JVM内存分配与回收

1.内存分配与回收策略 内存自动管理:自动化的解决了对象内存分配和回收对象内存的问题. 一般在堆上分配对象,也可能经过JTI编译后间接在栈上分配. 主要分配在新生代的Eden区,如果启动了本地线程分配缓冲(线程缓冲区TLAB)就优先在TLAB上分配. 2.对象优先在Eden 分配 大多数情况下,对象优先在新生代Eden区分配,当Eden区没有足够的空间分配时发生一次Minor GC. 如果Minor GC 之后筛选出的存活对象无法放入Survivor区那么这些对象就会被放入老年代. Minor

JVM内存分配与回收策略

对象优先在Eden分配 大多数情况下,对象在新生代Eden区中分配. 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC. Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为Java对象大多具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快.Major GC/Full GC:老年代GC,指发生在老年代的GC,出现了Major GC,经常会伴随至少一次Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略中就有直接进行

JVM虚拟机-03、JVM内存分配机制与垃圾回收算法

JVM虚拟机-03.JVM内存分配机制与垃圾回收算法 1 JVM内存分配与回收 1.1 对象优先在Eden区分配 大多数情况下,对象在新生代中?Eden?区分配.当?Eden?区没有足够空间进行分配时,虚拟机将发起一次Minor?GC.我们来进行实际测试一下.在测试之前我们先来看看?Minor?GC和Full?GC?有什么不同呢? Minor?GC/Young?GC:指发生新生代的的垃圾收集动作,MinorGC非常频繁,回收速度一般也比较快. Major?GC/Full?GC:一般会回收老年代,

【深入理解JVM】:内存分配与回收策略

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置. 本文中的内存分配策略指的是Serial / Serial Old收集器下(ParNew /

JVM内存分配策略,及垃圾回收算法

本人免费整理了Java高级资料,一共30G,需要自己领取;传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q 说起垃圾收集(Garbage Collection, GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Scala等)程序员在提升开发效率上获得了惊人的便利.理解GC,对于理解JVM和Java语言有着非常重要的作用.并且当我们需要排查各种内存溢

Java性能优化之JVM GC(垃圾回收机制)

Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行.当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成.GC优化很多时候就是减少stop-the-world 的发生. JVM GC回收哪个区域内的垃圾? 需要注意的是,JV

【JVM】JVM系列之垃圾回收(二)

一.为什么需要垃圾回收 如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收.除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此.所以,垃圾回收是必须的. 二.哪些内存需要进行垃圾回收 对于虚拟机中线程私有的区域,如程序计数器.虚拟机栈.本地方法栈都不需要进行垃圾回收,因为它们是自动进行的,随着线程的消亡而消亡,不需要我们去回收,比如栈的栈帧结构,当进入一个方法时,就会产生一个栈帧,栈帧大小也可以借助类信息确定,然后栈帧入栈,执行方法体,退出方法时,栈帧出