第三章 垃圾收集器与内存分配策略

第三章 垃圾收集器与内存分配策略

3.1 概述

垃圾回收器需要完成的三件事:

  • 哪些内存需要回收
  • 什么时候回收
  • 怎么回收

虚拟机栈、程序计数器、本地方法栈不需要垃圾回收;Java堆和方法区需要。

3.2 判断对象是否存活

3.2.1 引用计数算法

概述:每有一个引用,引用数就加1;每消失一个引用,引用数就减1;引用数到0了,就是要回收的对象。

缺点:无法解决循环引用问题 。如obja = objb和objb = obja。

3.2.2 可达性分析算法

概述:从“GC Roots”开始向下搜索,留下可达的,干掉不可达的。

可作为GC Roots的:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,如各个线程被调用的方法栈中使用到的参数、局部变量、临时变量等
  • 方法区中类静态属性引用的对象。
  • 在方法区中常量引用的对象。
  • 在本地方法栈中JNI(Native方法)引用的对象
  • Java虚拟机内部的引用
  • 所有被同步锁(synchronized关键字)持有的对象
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
  • 根据选用的垃圾收集器和当前回收的区域的不同,可有其他对象临时加入,如分代收集和局部回收

3.2.3 再谈引用

JDK1.2后,几个引用概念:

  • 强引用:程序中的引用赋值,类似Object obj = new Object();只要强引用存在,垃圾收集器就不会回收被引用的对象
  • 软引用:描述一些还有用,但不是必须的对象。被软引用的对象,在系统要发生内存溢出时,会将其进行二次回收,如果内存还不够,再抛出内存溢出异常。
  • 弱引用:类似软引用,但强度要低一些。只能生存到下一次垃圾收集器发生为止,无论内存够不够,被弱引用的对象都会被回收。
  • 虚引用:不会对对象的生存事件产生影响,也无法通过其获得对象的实例。只用作一个对象在被回收时,得到一个通知。

Java弱引用(WeakReference)的理解与使用_Java_零度的博客专栏-CSDN博客

3.2.4 生存还是死亡?

宣告对象死亡,要经过两次标记:

  • 没有与GC Roots相连接的引用链。
  • 没有在finalize()方法中逃脱。

判断有必要执行finalize()方法:

  • 该方法已被覆盖
  • 该方法从未被虚拟机调用过

如果判断对象有必要执行finalize()方法,就被放入一个低优先级的F-Queue中,等待执行该方法。“执行”,并不意味会等它运行结束。finalize()方法是对象逃脱回收的最后一次机会。

任何一个对象的finalize()方法只会执行一次,如果在下一次回收,该对象的finalize()方法不会执行。

现在不推荐使用finalize()方法。即使在关闭资源这类工作,因为有try-finally语句。

3.2.5 回收方法区

回收方法区性价比很低:

  • 回收的空间少
  • 回收的条件苛刻

Spring????

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP已经OSGi这类频繁自定义类加载器的场景中,通常需要Java虚拟机有类型卸载的能力,以保证不会对方法区造成过大的压力。

3.3 垃圾收集算法

推荐阅读:Richard Jones编写的《垃圾回收算法手册》第 2~4章。

从如何判断对象死亡出发,垃圾收集算法分为:

  • 引用计数式
  • 追踪式

3.3.1 分代收集理论

分代收集理论建立在两个分代假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的
  • 强分代假说:熬过越多次GC的对象越难以消亡

这两个假说奠定了垃圾收集器的一致设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象根据其年龄(对象熬过垃圾收集的次数)分配到不同的区域中储存。

由于跨代调用的情况存在,出现第三条假说:

  • 跨代引用假说:跨代引用相对同代引用来说仅占极少数

依据这条假说,我们将新生代上建立一个全局的数据结构,它将老年代分为若干小块,并标识出哪一块会发生跨代调用,当发生Minor GC时,只有这部分内存里的对象会被加入到GC Roots进行扫描

GC种类:

  • 部分收集(Partial GC):不是完整收集整个Java堆。
    • 新生代收集(Minor GC / Young GC):只收集新生代
    • 老年代收集(Major GC / Old GC):只收集老年代。只有CMS收集器会这样
    • 混合收集(Mixed GC):收集全部新生代和部分老年代。只有GI收集器有这种行为
  • 整堆收集(Full GC):收集整个Java堆和方法区

3.3.2 标记-清除 算法

算法分标记和清除两个阶段:

  • 首先标记需要回收的对象
  • 对标记的对象进行回收

也可以反过来:

  • 标记存活的对象
  • 回收未标记的对象

缺点:

  • 效率不稳定
  • 内存空间碎片化

3.3.3 标记-复制 算法

为解决上一种算法面对大量需要回收对象效率低下的问题。

概述:将内存划为大小相等的两块,每次只用一块,当这一块用完了,就将存活的对象复制到另一块,再将这块空间一次全部清理。

缺点:可用内存缩小了一半;而对于复制开销,由于存活下的对象很少,所以还是值得的。

Appel式回收:

  • 把新生代分为一块较大的Eden空间和两块较小的Survivor空间。
  • 每次内存只使用Eden空间和一块Survivor空间。
  • 发生垃圾收集时,将Eden和Survivor中存活的对象,复制到另一块Survivor空间,并清理那两块空间。
  • 当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(多为老年代)进行分配担保。

3.3.4 标记-整理 算法

算法过程:

  • 将存活的对象进行标记
  • 将标记的对象往内存的一端移动
  • 清理掉边界外的空间

3.7 选择合适的垃圾收集器

Epsilon收集器

  • 不能进行垃圾收集的“垃圾收集器”
  • 用于存活时间较短的应用(这时候只需要JVM可以合理分配内存,程序在堆耗尽之前就会退出)

垃圾收集器的职责:

  • 进行垃圾收集
  • 堆的管理与布局、对象的分配、与解释器、编译器、监控子系统的协作

选择垃圾收集器需要考虑的因素:

  • 应用程序的关注点是什么?吞吐量?延迟?内存占用?
  • 运行应用的基础设施如何?系统架构?处理器数量?分配的内存?操作系统?
  • 使用的JDK的发行商?版本号?

GC日志详解:

深入理解JAVA虚拟机—GC日志详解_Java_brushli的专栏-CSDN博客

3.8 实战

针对大对象的优化:

什么是大对象:需要大量连续内存的Java对象。

HotSpot虚拟机提供了“-XX:PretenureSizeThreshold"参数指定大于设定值的对象直接在老年代分配。

注意:该参数只对Serial和ParNew两款新生代收集器有效,HotSpot的其他新生代收集器,比如Parallel Scavenge并不支持此参数。可以使用ParNew加CMS收集器组合。

空间分配担保过程

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM内存结构: JVM内存分为如下五个部分: 1. 程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个程序计数器,是线程私有的,生命周期与线程相同。 2. Java虚拟机栈 Java虚拟机栈也是线程私有的,生命周期与线程相同。每个方法执行的时候,JVM都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法调用结束后,相应的栈帧也会被销毁。 3. 本地方法栈 本地方法栈也是线程私有的,它与Java虚拟机栈的作用非常相似,只不过它是为虚拟机使用到的Native方法服务。 4. JavaJava堆是JVM所管理的内存中最大的一块,也是所有线程共享的。Java堆是用于存储对象实例的内存区域,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的重点区域,也被称为GC堆。 5. 方法区 方法区也是线程共享的,用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK8之前,永久代(PermGen)是方法区的一部分。在JDK8时,永久代被彻底移除,使用了元空间(Metaspace)来代替。 内存分配策略JVM内存分配策略主要有以下几种: 1. 对象优先在Eden区分配 当JVM需要为新的对象分配内存时,会优先在Eden区进行分配。如果Eden区没有足够的空间,JVM会通过Minor GC回收部分内存空间。 2. 大对象直接进入老年代 如果要分配的对象大小超过了Eden区的一半,JVM会直接将该对象分配到老年代。这样做的目的是为了避免在Eden区内产生大量的垃圾对象,从而降低了Minor GC的频率。 3. 长期存活的对象进入老年代 JVM会为每个对象定义一个年龄计数器,当一个对象在Eden区经历了一次Minor GC后仍然存活,会被移动到Survivor区。在Survivor区中,对象会被继续观察,如果其存活时间达到了一定的阈值,就会被晋升到老年代中。这样做的目的是为了保证长期存活的对象能够在老年代中有足够的空间进行分配。 4. 空间分配担保 每次进行Minor GC时,JVM都会检查老年代的可用空间是否足够,如果足够,就可以安全地将所有存活的对象晋升到老年代中。如果不足,JVM会检查这次Minor GC之前的晋升到老年代的对象的平均大小与老年代的剩余空间的比值,如果比值大于某个阈值(通常为50%),那么这次Minor GC就会中止,JVM会进行Full GC来释放一些空间。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值