Java 堆空间(Heap Space)

概述

在Java程序中,堆是JVM内存空间中最大的一块,同时我们知道,每个线程都拥有一个虚拟机栈,但是堆不同,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。

在《Java虚拟机规范》中对Java堆的描述是:“所有 的对象实例以及数组都应当在堆上分配“,但是实际情况是几乎所有的对象都是分配在堆空间的,也有少部分情况比较特殊。这是因为由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

《Java虚拟机规范》里对Java堆进行了更进一步的细致划分:“Java虚拟机的堆内存分为新生代、老年代、永久代、Eden、Survivor……”,并且会根据区域的不同设计不同的垃圾回收期(GC)。

总结一下要点:

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。

  • Java堆区在JVM启动的时候即被创建,其空间大小也就确定了,堆是JVM管理的最大一块内存空间,并且堆内存的大小是可以调节的。

  • 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。

  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,简称 TLAB)。

  • 《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。

  • 从实际使用角度看:“几乎”所有的对象实例都在堆分配内存,但并非全部。因为还有一些对象是在栈上分配的(逃逸分析,标量替换)

  • 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。

在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。

  • 也就是触发了GC的时候,才会进行回收

  • 如果堆中对象马上被回收,那么用户线程就会收到影响,因为有stop the word

  • 堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。

Java堆空间结构

存储在JVM中的Java对象可以被划分为两类:

  • 一类对象的生命周期较短,这种对象的创建和消亡都十分迅速

  • 另一类对象的生命周期很长,在某些极端情况下甚至可以和JVM的生命周期保持一致

Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(oldGen)

其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)

  • 在默认的情况下(可以根据实际情况修改设置),新生区占堆空间的三分之一,老年代占堆空间的三分之二。

  • 在HotSpot中,Eden区空间和其他两个Survivor区空间的默认比例是 8 : 1 : 1。同时开发人员可以通果设置选项-XX:SurvivorRatio调整这个空间的比例。

  • 大部分对象都是在Eden区中被创建出来的。

  • 绝大多数的Java对象都在新生代中销毁(朝生暮死)

对象分配过程

对象分配是一个严谨且复杂的过程, 设计者需要考虑内存的分配,以为实际的分配与垃圾回收算法密切相关。

流程说明:

  • 创建出的新对象正常先放到Eden区,但是要判断Eden空间是否足够。

  • 如果足够,就放入Eden区。

  • 如果Eden区空间不足,会对Eden区进行垃圾回收(Minor GC),将伊甸区中不被引用的对象进行销毁操作,将新创建的对象放入Eden区。

  • 同时将Eden区存活的对象移动到Servivor0区

  • 如果之后触发垃圾回收机制,在Servivor0区中存活的对象会放到Servivor1区中,在经历垃圾回收机制Servivor1区存活的对象就在移动到Servivor0区,同时对象有一个"年龄"就是经历垃圾回收的次数,当经历过15次GC时,就会将这个对象移动到Old区。

  • 对于S0和S1区来讲:复制有交换,谁空谁是to

  • 如果Eden区内经历过GC后存活下来的对象转移到Servivor区,但是Servivor存放不下,就将这个对象移动到Old区

  • 在Old区,GC的次数相对少一些,当Old区不足时进行Major GC。

  • 如果进行了Major GC后仍然无法将对象进行储存,就会报OOM

流程图:

关于GC的说明(Minor GC、Major GC、Full GC)

  1. JVM的调优的一个环节,也就是垃圾收集GC,我们需要尽量的避免垃圾回收,因为在垃圾回收的过程中,容易出现STW(Stop the World)的问题,而 Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上。

  1. JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。针对Hotspot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(FullGC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:

  • 新生代收集(Minor GC/Young GC):只是新生代(Eden,s0,s1)的垃圾收集

  • 老年代收集(Major GC/Old GC):只是老年代的圾收集。

  • 目前,只有CMS GC会有单独收集老年代的行为。

  • 注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。

  • 混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有G1 GC会有这种行为

  • 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。

MinorGC

  • 当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满。Survivor满不会主动引发GC,在Eden区满的时候,会顺带触发s0区的GC,也就是被动触发GC(每次Minor GC会清理年轻代的内存)

  • 因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。

  • Minor GC会引发STW(Stop The World),暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行

Major/Full GC

  1. 指发生在老年代的GC,对象从老年代消失时,我们说 “Major Gc” 或 “Full GC” 发生了

  1. 出现了MajorGC,经常会伴随至少一次的Minor GC。(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行MajorGC的策略选择过程)

  • 也就是在老年代空间不足时,会先尝试触发Minor GC,如果之后空间还不足,则触发Major GC

  1. Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。

  1. 如果Major GC后,内存还不足,就会报OOM

Major GC通常是跟full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的full GC还是old GC。

Partial GC(部分GC): 并不收集整个GC堆的模式

  • Young GC:只收集young gen的GC。

  • Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式。

  • Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式。

Full GC:收集整个堆,包括young gen、old gen、perm gen永久代 (如果存在的话)等所有部分的模式。

  • young GC:当young gen中的eden区分配满的时候触发。注意young GC中有部分存活对象会晋升到old gen,所以young GC后old gen的占用量通常会有所升高。

  • full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。

为对象分配内存 (TLAB)

为什么要有TLAB

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据

  • 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的

  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。

如果有了TLAB,每个线程将自己要操控的对象放到自己的TLAB区域,就能在一定程度上避免了线程安全问题。

TLAB说明

  1. 从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。

  1. 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。

  1. 很多OpenJDK衍生出来的JVM都提供了TLAB的设计。

  1. 尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选。

  1. 在程序中,开发人员可以通过选项-XX:UseTLAB设置是否开启TLAB空间。

  1. 默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,当然我们可以通过选项XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。

  1. 一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。

TLAB分配流程图:

Java堆空间(Heap Space)相关推荐

  1. Java 堆空间(Heap Space)

    Java 堆空间(Heap Space) 概述 在Java程序中,堆是JVM内存空间中最大的一块,同时我们知道,每个线程都拥有一个虚拟机栈,但是堆不同,Java堆是被所有线程共享的一块内存区域,在虚拟 ...

  2. qotd服务_QOTD:Java线程与Java堆空间

    qotd服务 以下问题很常见,并且与OutOfMemoryError有关:在JVM线程创建过程和JVM线程容量期间无法创建新的本机线程问题. 这也是我向新技术候选人(高级职位)提出的典型面试问题. 我 ...

  3. QOTD:Java线程与Java堆空间

    以下问题很常见,并且与OutOfMemoryError有关:在JVM线程创建过程和JVM线程容量期间无法创建新的本机线程问题. 这也是我向新技术候选人(高级职位)提出的典型面试问题. 我建议您在查看答 ...

  4. Java堆空间– JRockit和IBM VM

    本文将为您提供JRockit Java堆空间与HotSpot VM的概述. 它还将为您提供有关JRockit和HotSpot的Oracle未来计划的一些背景知识. Oracle JRockit VM ...

  5. java堆空间(内存)

    当Java程序开始运行时,JVM会从操作系统获取一些内存.JVM使用这些内存,这些内存的一部分就是堆内存.堆内存通常在存储地址的底层,向上排列.当一个对象通过new关键字或通过其他方式创建后,对象从堆 ...

  6. OOM系列之一:java.lang.OutOfMemoryError: Java堆空间问题详解

    第一篇:java.lang.OutOfMemoryError: Java heap space Java 应用程序只允许使用有限的内存量.此限制是在应用程序启动期间指定的.为了让事情变得更复杂,Jav ...

  7. OutOfMemoryError:Java堆空间–分析和解决方法

    java.lang.OutOfMemoryError:Java堆问题是在支持或开发复杂的Java EE应用程序时可能会遇到的最复杂的问题之一. 这篇简短的文章将为您提供此JVM HotSpot Out ...

  8. IBM JDK的Java堆空间的碎片问题

    欢迎大家访问我的个人网站 萌萌的IT人,后续所有的文章都会在此发布 ------------------------------------------------------------------ ...

  9. Java堆空间,本机堆和内存问题

    最近,我在和一个朋友讨论为什么Java进程使用的内存比启动Java进程时设置的最大堆多. 代码创建的所有Java对象都是在Java堆空间内创建的,其大小由-Xmx选项定义. 但是一个Java进程由很多 ...

最新文章

  1. LeetCode 589. N-ary Tree Preorder Traversal-多子节点树前序遍历--递归,迭代--反向压栈--C++解法
  2. 图像检索中为什么仍用BOW和LSH
  3. linux 非登录shell自动,Linux登录shell和非登录(交互式shell)环境变量配置
  4. 应用程序对象正在关闭_Windows核心编程-内核对象
  5. SpringBoot中通过重写WebMvcConfigurer的addCorsMapping方法实现后台服务解决跨域问题
  6. CodeForces - 160E Buses and People(线段树+三维偏序)
  7. 你确定你真的理解“双亲委派“了吗?!
  8. nc拉单查询模版_用友NC的模板管理_操作步骤
  9. Android camera开发(9)---Dual Camera(MTK solution)开案说明
  10. JavaScript 中的 this 与闭包详解
  11. java代码块执行顺序_JAVA代码块执行顺序分析测试
  12. 同时安装 Python 2 和 Python 3环境下 pip 的使用
  13. 成功EDM电子邮件营销的要素和目标分析
  14. 《信号与系统》(吴京)部分课后习题答案与解析——第五章(PART3)(系统)
  15. 【MPLAB X IDE】04:找不到目标器件,你必须连接一个目标器件来使用PICkit 3
  16. PPT(默认设置快捷键的使用)
  17. 书小宅之网页设计——二次贝塞尔曲线和三次贝塞尔曲线
  18. Webpack 使用
  19. 视频去水印,图集去水印工具分享
  20. 免费ARP(gratuitousARP)

热门文章

  1. GBase 8s 特性简介
  2. 华为TE10一体化视频会议终端
  3. Lucene基础(二)--索引的操作
  4. 数据库基础之位图索引
  5. C语言小游戏解决生活中的数学问题
  6. Chrome和edge报STATUS_STACK_BUFFER_OVERRUN错误的处理办法
  7. ORACLE 性能优化示例
  8. 数学之美2 - 概率篇
  9. HADOOP HA之NameNode HA集群配置与应用
  10. 听音乐用什么蓝牙耳机好?听音乐音质好的蓝牙耳机推荐