Java 内存分配与垃圾回收机制

标签: JVM 编程点滴 | 发表时间:2015-07-16 04:00 | 作者:
出处:http://RincLiu.com/

程序计数器:

  • 用于指示当前线程执行的指令行号,字节码解释器通过改变它的值选取下一条待执行的指令;

  • 分支、循环、跳转、异常处理、线程恢复都需要依赖它;

  • 它是线程私有的;

:

  • 存储和方法执行相关的信息:栈帧(Stack Frame);

  • 栈帧包含: 局部变量表(基本数据类型和引用)、操作栈、动态链接、方法出口等信息;

  • 每一个方法从被调用到运行结束都对应着栈帧从入栈到出栈的过程;

  • 根据方法类型,可分为虚拟机栈和本地方法栈;

  • 它也是线程私有的;

:

  • 存储对象实例,是 GC 管理的主要区域,

  • 根据对象生存周期,它被分为新生代(YoungGen)和老年代(TenuredGen),而新生代又分为 Eden、FromSurvivor 和 ToSurvivor;

  • 它也是所有线程共享的;

方法区

  • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译的代码等;

  • HotSpot 虚拟机通常将它和”永久代”(PermanentGen)等同,该区域一般不会发生 GC;

  • 它也是所有线程共享的;

直接内存

  • 它与 NIO 中的 Channel 和 Buffer 有关,采用 native 方法直接分配堆外内存;

  • 它通过存储在 Java 堆内存中 DirectByteBuffer 对象中的引用进行 I/O 操作;

“内存溢出”与”内存泄漏”:

  • “内存溢出”是指无法再分配所需的内存;

  • “内存泄漏”是指已分配的内存无法释放,多次泄露会导致内存溢出;

  • 常见的情况是 new 的对象用完后没有及时 delete(回收),造成内存无法释放;

  • 发生的位置:

    • 堆( Xmx/ Xms):对象实例未及时释放或生命周期过长;

    • 栈( Xss):请求的栈深度(方法调用层级或递归)超出允许最大值,或无法分配更多内存;

    • 常量区( XX:PermSize/ XX:MaxPermSize):通常是 String 相关操作导致的,比如 String.intern(),如果该对象不存在就会添加到常量池;

    • 直接内存( XX:MaxDirectMemorySize):其大小默认与最大堆内存一样;

垃圾回收算法:

引用计数

  • 标记引用数,检查是否为 0;

  • 无法解决循环引用问题;

分代

  • 根据对象生存周期将内存分为:新生代(Eden/Survivor)、老年代(TenuredGen)、永久代(PermanentGen);

  • 绝大部分对象存活周期很短,所以新生代一般是一个 80% 的 Eden + 两个 10% 的 Survivor;

  • 新生代一般采用”拷贝”算法;老年代一般采用”标记清理”或”标记整理”算法;

拷贝

  • 将可用内存分为两个部分,每次只使用其中一块,避免产生大量内存碎片;

  • 如果使用中的那一块内存用完,就将存活的对象拷贝到另一块未使用的内存上,并将使用过的那一块全部清理;

  • 实际拷贝时是将 Eden 空间和其中一个 Survivor 中存活的对象拷贝到另一个 Survivor 中;

根搜索

  • 从所有 GC Root 对象向下搜索,如果和指定对象之间没有可达的引用路径,则可被回收;

  • Root 对象包括:

    • 虚拟机栈中的引用对象;

    • 本地方法栈中的 JNI 引用对象;

    • 方法区中的静态引用对象和常量引用对象;

标记-清理

  • 搜索结束后,没有引用链的对象会被标记,一般会经历两次标记;

  • 如果 finallize() 方法没有重写或者已经调用过,则会将该对象放到 F-Queue 队列中等待 Finallizer 线程执行清理;

  • 如果该对象此时要防止被回收,只需要将自己与其他存活对象建立引用关联即可;

  • 缺点:标记和清理过程效率低,并且会产生大量内存碎片;如果以后程序无法分配到足够的连续内存,会再次触发 GC;

标记-整理

  • 标记后并不是直接清理,而是将所有存活对象都向一端移动,然后清理掉边界以外的内存;

垃圾回收器:

Serial

  • 回收时需要暂停所有工作线程;

  • 简单高效(没有线程交互开销),是 Client 模式下的默认新生代收集器;

Serial Old

  • 它是 Serial 的老年代版本,同样单线程;

ParNew

  • 在 Serial 基础上添加了多线程支持,是第一款并发收集器,也是 Server 模式下的默认新生代收集器;

  • 除了 Serial,目前只有 ParNew 能和 CMS 配合工作;

CMS(Concurrent Mark Sweep)

  • 它追求的是最短停顿时间,适合频繁与用户交互的场景;

  • 分为四个步骤:

    • 初始标记:快速标记 Root 对象能直接关联的对象(需要暂停用户线程);

    • 并发标记:跟踪查找引用链;

    • 重新标记:修正上一步并发期间的标记(需要暂停用户线程);

    • 并发清理;

  • 缺点:

    • 占用 CPU 资源,影响吞吐量;

    • 无法处理并行过程中新产生的垃圾,只能等待下次 GC;

    • 由于采用“标记-清理”算法,会产生内存碎片;

Parallel Scavenge

  • 它追求的是更大的吞吐量(用户代码运行时间与总时间的比值),适合后台任务;

  • 如果粗暴地减小新生代,虽然可以减小停顿时间,但会是 GC 变得频繁,并且牺牲了吞吐量;

  • 该收集器除了可以设置最大停顿时间和吞吐量,还可以开启自适应策略动态调整参数;

Parallel Old

  • 它是 Parallel Scavenge 的老年代版本;

G1

  • 基于“标记-整理”算法,不会产生内存碎片;

  • 可以精确控制某个时间段内的最大 GC 停顿时间;

  • 之前的回收器的收集范围都是整个新生代或老年代,而 G1 将则它们分为多个大小固定的区(Region),并且跟踪其垃圾堆积程度,每次都优先回收垃圾最多的区域;

内存分配与 GC 触发策略:

  • 一般新对象优先分配在新生代的 Eden,如果空间不够则发起一次 Minor GC;

  • 大对象(很长的字符串或数组)直接分配在老年代,避免 GC 时发生大量内存拷贝;

  • 更新对象年龄:

    • 虚拟机给每个对象定义了 Age 计数器,Eden 中新创建的对象 Age 为 0;

    • Eden 中的对象若在 Minor GC 后存活,则移入 Survivor(如果可以容纳的话),且 Age++;

    • Survivor 中的对象若经历 Minor GC 后仍然存活,则 Age++;

    • 当 Survivor 中某对象的Age超过阈值(默认 15)时,会被移入老年代;

  • 空间分配担保:

    • 若 Minor GC后 仍有大量存活对象(Survivor 空间不够),则需要老年代进行空间分配担保;

    • 检测之前移入老年代的对象平均大小,如果大于老年代剩余空间,则进行一次 Full GC 让老年代释放部分空间;

    • 如果小于则进一步检测 HandlePromotionFailure 设置是否允许担保失败,如果不允许则仍会进行 Full GC;


参考: 《深入理解 Java 虚拟机(第 2 版):JVM 高级特性与最佳实践》

相关 [java 内存 垃圾回收] 推荐:

Java内存与垃圾回收调优

- - ImportNew
要了解Java垃圾收集机制,先理解 JVM内存模式是非常重要的. 今天我们将会了解JVM内存的各个部分、如何监控以及 垃圾收集调优. 正如你从上面的图片看到的,JVM内存被分成多个独立的部分. 广泛地说,JVM堆内存被分为两部分 ——年轻代 (Young Generation)和 老年代 (Old Generation).

探秘Java虚拟机——内存管理与垃圾回收

- - 编程语言 - ITeye博客
探秘Java虚拟机——内存管理与垃圾回收. 本文主要是基于Sun JDK 1.6 Garbage Collector(作者:毕玄)的整理与总结,原文请读者在网上搜索. 1、Java虚拟机运行时的数据区. 2、常用的内存区域调节参数. -Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.

Java 内存分配与垃圾回收机制

- - RincLiu.com
用于指示当前线程执行的指令行号,字节码解释器通过改变它的值选取下一条待执行的指令;. 分支、循环、跳转、异常处理、线程恢复都需要依赖它;. 存储和方法执行相关的信息:栈帧(Stack Frame);. 栈帧包含: 局部变量表(基本数据类型和引用)、操作栈、动态链接、方法出口等信息;. 每一个方法从被调用到运行结束都对应着栈帧从入栈到出栈的过程;.

Java 内存结构与垃圾回收备忘

- - Java - 编程语言 - ITeye博客
原文地址: https://dzone.com/articles/java-memory-architecture-model-garbage-collection. 研究Java垃圾回收机制,看到一篇好文,先转载,做深入研究. 下图展示了 Java 堆内存模型,以及运行在 Java 虚拟机中任意 Java 应用的 PermGen (内存永久保存区域),下面的比率展示了 JVM 各代类型允许的内存大小分配情况,所有的数据均适用于 Java 1.7 及以下版本.

Java垃圾回收调优

- - 编程语言 - ITeye博客
在Java中,通常通讯类型的服务器对GC(Garbage Collection)比较敏感. 通常通讯服务器每秒需要处理大量进出的数据包,需要解析,分解成不同的业务逻辑对象并做相关的业务处理,这样会导致大量的临时对象被创建和回收. 同时服务器如果需要同时保存用户状态的话,又会产生很多永久的对象,比如用户session.

Java中的垃圾回收

- - Java译站
前文中对标记删除算法的介绍更多还是偏理论性质的. 实践中,为了更好地满足现实的场景及需求,还需要对算法进行大量的调整. 举个简单的例子,我们来看下JVM需要记录哪些信息才能让我们得以安全地分配对象空间. 碎片及整理(Fragmenting and Compacting). JVM在清除不可达对象之后,还得确保它们所在的空间是可以进行复用的.

Java/JVM垃圾回收机制和算法总结

- - ITeye博客
Java垃圾回收器是Java虚拟机(JVM)的三个重要模块(另外两个是解释器和多线程机制)之一,为应用程序提供内存的 自动分配(Memory Allocation)、自动回收(Garbage Collect)功能,这两个操作都发生在Java堆上(一段内存快). 某一个时点,一个对象如果有一个以上的引用(Rreference)指向它,那么该对象就为活着的(Live),否则死亡(Dead),视为垃圾,可被垃圾回收器回收再利用.

详细介绍Java垃圾回收机制 (转)

- - Java - 编程语言 - ITeye博客
详细介绍Java垃圾回收机制. 垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变. 垃圾收集的目的在于清除不再使用的对象. GC通过确定对象是否被活动对象引用来确定是否收集该对象.

高吞吐低延迟Java应用的垃圾回收优化

- - ImportNew
高性能应用构成了现代网络的支柱. LinkedIn有许多内部高吞吐量服务来满足每秒数千次的用户请求. 要优化用户体验,低延迟地响应这些请求非常重要. 比如说,用户经常用到的一个功能是了解动态信息——不断更新的专业活动和内容的列表. 动态信息在LinkedIn随处可见,包括公司页面,学校页面以及最重要的主页.

Java垃圾回收机制与引用类型

- - 编程语言 - ITeye博客
Java语言的一个重要特性是引入了自动的内存管理机制,使得开发人员不用自己来管理应用中的内存. C/C++开发人员需要通过 malloc/ free和 new/ delete等函数来显式的分配和释放内存. 这对开发人员提出了比较高的要求,容易造成内存访问错误和内存泄露等问题. 一个常见的问题是会产生“悬挂引用(danglingreferences)”,即一个对象引用所指向的内存区块已经被错误的回收并重新分配给新的对象了,程序如果继续使用这个引用的话会造成不可预期的结果.