Java 垃圾回收器与内存分配策略 JVM

什么是垃圾回收?

在 Java 开发的过程中,万物皆为对象,产生的对象存在生命周期,也就是对象存在存活的时间;在程序运行过程中,会产生完成自身使命的对象,这个时候对象就成为了程序当中的垃圾,不回收的话会一直占用内存,对于程序的性能,内存是有一定影响的;在 C++ 中,开发人员是自己进行垃圾回收的,但是在 Java 中,JVM 里面存在多种垃圾回收器以及多种垃圾回收算法,底层的垃圾回收一般情况下不需要开发人员关心,但是在某些特定的场景下,需要开发人员掌握 JVM 垃圾回收机制,更好的解决一些程序问题;

哪些位置内存需要回收?

Java 堆中的对象;里面具有分代的内存划分,当内存满的时候,就会发生垃圾回收;常见的代的划分为 新生代 + 老年代 或者 划分为内存相等的 Region(G1)

Java 方法区的某些变量,常量池中的某些变量;由于垃圾回收的性价比太低,一般被称作永久代,不做垃圾回收;

注意:在不同的垃圾回收器中,堆内存的划分是不一样的;

四种引用关系

根据对象的引用的不同,不同的对象引用回收的时机是不一样的;

强引用

使用最普遍的引用。
只要引用链没有断开,强引用就不会断开。- 当内存空间不足,抛出OutOfMemoryError终止程序也不会回收具有强引用的对象。
通过将对象设置为null来弱化引用,使其被回收

//都是强引用
Object object = new Object();
String str = "scc";

软引用

对象处在有用但非必须的状态
只有当内存空间不足时, GC会回收该引用的对象的内存。
可以用来实现高速缓存(作用)--比如网页缓存、图片缓存

// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));

弱引用:描述那些非必须的对象;对象在下一次垃圾回收的时候回收,不管内存够不够都会回收

//ResourceWeakReference弱引用
Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

虚引用:最弱的一种引用关系;仅仅用于垃圾回收器回收对象的时候,给对象发送一个系统通知;

在这里插入图片描述
强:有用且必须
软:有用非必须
弱:非必须
虚:没用非必须

如何判断一个对象是不是需要回收?

1、使用引用计数法判断
2、使用可达性分析算法判断

下面对于上述的两种算法进行阐述:

在垃圾回收机制中,传统的存在简单的引用计数算法,但是在考虑一个对象引用的时候,需要额外的多种情况需要考虑(比如对象之间的相互引用); JVM 没有使用引用计数算法;使用了可达性分析算法,当一个对象到 GC Roots 不可达的时候, finalize() 方法执行之后,就会被垃圾回收器回收;

所谓的引用计数法:维护指向对象的引用的个数,也就是指向对象的指针的个数;当一个对象的引用计数为 0 的时候,需要进行回收;

所谓的 GC Roots 算法,判断一个对象到 Gc Roots这些根对象之间存不存在引用链,存在的话,不回收,不存在的话,回收掉即可;

GC Roots 对象有哪些?

1、虚拟机栈中引用的对象;开发人员写的方法里面的变量指向的对象;比如:Deque<Integer> numbers = new LinkedList<>();

2、方法区中的静态属性引用的对象;类中定义的静态的类变量引用的对象;例如:private static String s1 = "Java";

3、本地方法栈中引用的对象,native 方法里面的变量指向的对象

4、Java 虚拟机内部的引用,比如基本数据类型对应的 Class 对象; JVM 里面常驻的异常对象,比如空指针异常 NullPointException

5、被同步锁 synchornized 持有的对象;

如何回收?

创建的垃圾回收算法,是存在指导思想的,网络上面大多数博客直接讲各种算法,有一些不合适;

垃圾回收算法的指导思想

1、弱分代假说:绝大多数的对象存活时间较短
2、强分代假说:熬过多次的垃圾回收过程的对象难以消失;
3、跨代引用假说:新生代指向了老年代,这种情况比较少

常用的垃圾回收算法 看图即可明白回收的思路

基于上面的垃圾回收算法指导思想,相关科研人员以及开发人员,创建了下面的比较经典的垃圾回收算法,垃圾回收算法是与时俱进的,可能会出现更好的垃圾清除算法

标记 - 清除算法

在这里插入图片描述

标记 - 整理算法

在这里插入图片描述

标记 - 复制算法

在这里插入图片描述

Java 堆内存的垃圾回收

限定条件是在 Java 堆中进行垃圾回收;

Java 堆的内存划分区域图示 (不同的垃圾收集器可能划分的方式不一样 G1 就不是下面的划分方式)

在 Java 堆中是只存在新生代以及老年代的,是没有永久代的,只有在方法区存在的是永久代;

Java 堆 = 新生代 + 老年代
在这里插入图片描述
方法区的垃圾回收:有人称为元空间,或者永久代,因为在方法区进行的垃圾回收的效果太差,或者说回收的性价比太低;

常见的垃圾回收器垃圾主流的垃圾回收算法是什么?

垃圾收集算法是内存回收的方法论,垃圾收集器就是内存回收的实践者;换句话说:垃圾回收器与垃圾回收算法之间的逻辑是,垃圾回收器实现了垃圾回收算法;

JVM 提供的可以选择的垃圾回收器有很多,根据需要,因地制宜最好;
不同的垃圾回收器实现了不同的垃圾回收算法,但是经典的垃圾回收算法还是:标记 - 清除;标记 - 复制,标记 - 整理;

经典的垃圾回收器

Serial

在这里插入图片描述

新生代收集器

单线程工作的收集器;在垃圾回收的时候会 Stop The World ,也就是运行垃圾回收线程的时候,所有的用户线程都需要停止;对于用户的体验是有一定的影响的,想一下自己的电脑每小时五分钟时间卡死,单纯为了垃圾回收,这样是难以忍受的,但是实际的Serial 回收的时间在几百毫秒中;

HotSpot 在客户端模式下面的默认垃圾回收器,简单高效,目前可以将 Stop The World 的停顿时间控制在 100 毫秒以内;

ParNew

在这里插入图片描述
新生代收集器

本质上是 Serial 的多线程并发版本,在 HotSpot 中已经退出了历史舞台;

在单核的机器上面,使用起来不如 Serial 垃圾收集器的;

Parallel Scavenge

新生代收集器

基于 标记 - 复制 算法实现的收集器;并发收集的多线程收集器;

Parallel Scavenge 自身的特点:达到可以控制的吞吐量;

吞吐量:运行用户代码的时间 / 运行用户代码的时间 + 运行垃圾收集的时间

Serial Old

在这里插入图片描述
Serial 是 Serial 收集器的老年代版本;

同样是一个单线程收集器;基于 标记 - 整理算法;

主要意义:提供客户端模式下面的 HotSpot 虚拟机使用

Parallel Old

在这里插入图片描述

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并行收集 基于标记整理算法实现;

CMS

一款以获取最短回收停顿时间为设计目标的垃圾回收器,现在的基于 B/S 架构的软件,比较关注的点是系统的响应时间,也就是系统停顿的时间尽可能的短一些,从而提升良好的交互体验;CMS 收集器是比较符合这种应用场景的;

优点:低停顿,可以并发执行

CMS 的实现原理(图示):
在这里插入图片描述

缺点:
1、当系统的 cpu 核心数比较少的时候,对于整个系统的资源的开销是比较大的;CMS 默认启动的垃圾回收的核心线程数目:(处理器核心数量 + 3)/ 4;

2、CMS 没有办法处理浮动垃圾;当 CMS 在并发标记以及并发清理的时候,由于程序在继续的运行,所以任然在产生垃圾,但是这一部分的垃圾是在标记之后产生的,所以在此次的垃圾回收中,是不会被清理的,只能在下一次的垃圾回收中清理这些浮动垃圾;

3、该垃圾收集器本身是基于 标记 - 清除 算法的,所以存在内存空间碎片的问题;在一定程度上面是需要整理内存碎片的;

Garbage First (G1 回收器)

面向服务端应用的垃圾回收器;CMS 的替代者以及继承人;

JDK9 的时候,CMS 沦落成为不推荐使用的垃圾回收器了;

G1垃圾回收器的实现思路:
以往的垃圾回收器中,关注点是在某一个分代中进行垃圾回收,但是在 G1 垃圾收集器中,回收垃圾的衡量标准,不是清理某一个代,而是分析哪儿快内存中的垃圾数量多,回收的效益比较大,这也就是形成了后面的 Mixed GC 模式;

下面是内存布局示意图:
在这里插入图片描述

G1 的内存布局:

Java 堆分为多个大小区域相等的 Region 区域,在每个区域中,可以根据需要,扮演新生代的Eden 空间 ,Survior 空间, 老年代空间;

Region 中存在 Humoungous 区域,专门存放大对象;`(大对象就是大小超过一个 Region 一般的对象)`;

用户可以指定期望的停顿时间;通常设置为 100 ~ 300 毫秒之间是比较合理的;

后序的 G1 垃圾回收器进行持续的优化会使得 G1 在各个方面尽可能比 CMS 要更好一些;

低延迟的垃圾回收器

Shenandoah

ZGC

JDK8 默认的垃圾回收器

java8默认使用的应该是 Parallel Scavenge + Parallel Old

常见的题目

常用的垃圾回收器有哪些?

查看上面的总结即可

详细介绍下CMS?

初始标记 并发标记 重新标记 并发清理

CMS 是垃圾回收器的一种,Concurrent Mark Sweep 基于 标记 - 清除算法的并发低停顿收集器;

垃圾回收线程可以和用户线程并发执行,减少系统的停顿,提升用户的体验;对于 B/S 架构的开发是有帮助的;

什么情况下老年代会发生GC?

1、创建新的对象的时候,先会在新生区进行内存的分配,当经历了多次的垃圾回收之后,对象依然存活,一般是 15 次,会将对象放置到老年代中,当老年代的内存空间满了之后,会发生一次 Major GC ;

2、发生 full GC 的时候,会进行老年代的回收

Full GC:收集整个 Java 堆以及方法区的对象;

JVM怎么判断一个对象是否是需要清理?

JVM 没有使用引用计数法,使用的是可达性分析算法;判断对象的引用能不能到达 GC Roots ,可以到达的话不是垃圾对象,不可以到达的话就是需要回收的对象;

使用可达性分析算法分析出来了,当前对象与 GC Roots 上面的额对象没有引用的时候,不是立刻马上进行垃圾回收的,最多再进行两次标记;

只标记一次的情况:对象没有重写 finalize() 方法,或者这个方法被执行过了,那么这个对象可以回收了;

标记两次的情况:对象重写了 finalize 方法,还没有被虚拟机执行,那么进行第二次标记,将对象放置到 F- Queue 等待队列中执行 finalize() 方法,如果在这个时候和 GC Roots 对象建立起来了联系,那么对象是不用回收的,否则就是需要回收的;

参考

周志明 深入理解 Java 虚拟机 第三版

一文理解JVM虚拟机(内存、垃圾回收、性能优化)

G1、ZGC、ShenandoahGC高性能收集器深入剖析

  • 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、付费专栏及课程。

余额充值