JVM从入门到入土之JVM的内存分配策略和垃圾回收器(上)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

絮叨


前面的章节

Java与C++之间有一堵内存动态分配和垃圾收集的高墙 外面的人想进去,里面的人想出来


概述


  • 那些内存需要回收?什么时候进行回收?如何进行回收?
  • 程序计数器、虚拟机栈、本地方法栈这三部分随着线程而生,随着线程而灭。栈中的栈帧随着方法有序的进出。每一个栈帧中分配内- 存是在类结构确定下来就已知(JIT会在编译时进行些许优化)
  • 因此程序计数器、虚拟机栈、本地方法栈这三者内存分配和回收都具备确定性,垃圾回收再这几块不需要你过多的考虑。他们会随着- 线程的回收而自动进行回收。
  • Java堆和方法区(Java堆中一个逻辑部分),所以说垃圾回收也是主要考虑到这块的内存的回收和利用。


对象已死?


  • 需要判断Java堆中有哪些对象是活着或者死去(即不可能被任何途径使用的对象)


引用计数算法


  • 引用计数算法很优秀应用很广泛,但是它很难解决对象之间循环依赖的而导致的问题
  • 引用计数算法的存在的缺陷,如上
  • JavaVM不是通过引用计数来进行垃圾回收的(来判断对象是否存活)


根可达性分析算法(GC Roots Tracing)


Java中可以作为GC Roots对象包括以下几种:

  • 虚拟机栈(栈帧中本地变量表)中引用的对象;
  • 方法区中类的静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(一般只Native方法)的引用对象-

示例如下


再谈引用



1.2之前Java中对象只有:引用和未引用两种状态

1.2之后进行了扩充:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)

  • Strong Reference:只要强引用还存在,垃圾回收器就不会进行回收
  • Soft Reference:一些还有用,但是非必需的对象。系统将要发生OOM时,会将这些对象列入回收范围,并进行第二次垃圾回收。如- 果回收之后内存还不够则会抛出OOM。Java中提供SoftReference来实现。
  • Weak Reference:描述非必需对象。弱引用的关联的对象只能生存到下一次垃圾回收器发生之前。无论内存是否充足,都会回收掉弱- 引用关联的对象。WeakReference类
  • Phantom Reference:最弱的一种引用。一个对象是否有虚引用的存在都不会对其生存时间造成影响,也无法通过虚引用来获取一个- 对象的实例。为一个对象设置、虚引用的唯一目的就是希望在该对象被垃圾回收器回收时收到一个系统通知。PhantomReference类。


生存还是死亡


根搜索法不可达的对象,还有两次标记的过程,进行自救。

过程:

在跟搜索算法不可达的对象,并将第一次被标记并且进行一次筛选。筛选条件是:此对象是否有必要调用finalize()方法。当对象没有覆盖finalize或方法已被虚拟机执行了,虚拟机 会认为以上两种情况没有必要执行。

如果这个对象被判定为有必要执行finalize()方法。该对象将会被F-Queue的队列中,稍后虚拟机将建立一个低优先级的Finalizer线程去执行。这里的执行指的是虚拟机会触发该 方法,但是并不承诺等待他运行结束(原因:finalizer执行很慢或死循环,导致队列中其他的对象永远在等待或内存溢出)。finalizer方法是对象逃离死亡的最后一次机会,对象只要finalizer中 拯救自己(建立自己引用)第二次标记的时候该对象就被移除回收队列。如果没有拯救,那么很快不久就被回收。但是如果对象的finalizer方法执行了,但是可能该对象还存活着。


实例代码如下:


public static FinalizerEscapeGC SAVE_HOOK = null;
    public void isAlive() {
        System.out.println("yes, i am still alive!!!");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalizer method execute");
        FinalizerEscapeGC.SAVE_HOOK = this;
    }
    public static void main(String[] args) throws Exception {
        SAVE_HOOK = new FinalizerEscapeGC();
        // 对象第一次进行成功的拯救
        SAVE_HOOK = null;
        System.gc();
        // 因为finalizer方法的优先级很低,所以暂停0.5s,以等待他运行
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead.");
        }
        // 下面的代码和上面的代码一样。但是对象却自救失败了。
        // 对象第二次进行成功的拯救
        SAVE_HOOK = null;
        System.gc();
        // 因为finalizer方法的优先级很低,所以暂停0.5s,以等待他运行
        Thread.sleep(600);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead.");
        }
    }
复制代码


回收方法区


  • 方法区有被认为是HotSpot虚拟机中的永久代
  • 误区:Java虚拟机规范中说过,不要求虚拟机在方法区实现垃圾收集,“性价比”很低;回收堆中新生代一般可以回收70%-95%,而永久- 代的垃圾回收远远低于此
  • 永久代垃圾回收主要包括:废弃常量和无用的类。
  • 回收废弃常量和回收Java堆中对象十分相似。以常量池中的字面值回收为例,“abc”字符串已进入常量池,但是系统中没有任何一个- 地方
  • 引用字符串“abc”,也没有其他地方引用。如果这个时候发生内存回收,如果必要的话,该字符串变量则会被回收掉。


判定一个类是否是无用类则比较复杂。

  • 该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例
  • 该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法 即时是满足以上三个条件,虚拟机也仅仅是可以进行回收。不像对象一样,一定会被回收。对类的回收,Hotspot

提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading 其中前两个参数在product版虚拟机中已经支持,最后一个需要FastDebug版的虚拟机支持。


垃圾收集算法


标记 - 清除算法

  1. 首先标记处所有需要回收的对象;其次在标记完成之后统一回收掉所有被标记的对象。
  2. 最基础的算法。后续的算法都是基于此,并针对其缺陷进行改进的得到的。
  3. 缺点:
  • 效率(不高)标记和清除的效率都不高
  • 空间问题,标记清除后会导致大量不连续的内存碎片,而碎片过多可能会导致以后程序在运作的过程中,分配较大对象时无法找到足够的连续内存而不得不触发另一次垃圾回收动作
  1. 图解


标记 - 复制算法(典型的用空间换时间的手法)

  1. 为了解决效率问题而出现的复制算法
  2. 复制算法:
  • 内容:将可用内存按容量划分成大小相等的两块。每次只是使用其中的一半,将快使用完成后将存活的对象 复制到另外一块内存上去,然后再把使用过的内存一次清理掉。


  • 好处1:每次都是对整块内存进行回收,减少了内存碎片的复杂情况
  • 好处2:移动时只需移动堆顶指针,按照顺序分配即可,简单高效
  • 缺点:内存缩小为原来的一半,有点浪费内存空间 3.图解


  1. 现代的商业虚拟机都是都是采用这种算法来回收新生代内存。新生代中对象98%都是朝生夕死。并非严格按照1:1。而是按照8:1。
  2. 1块较大的Eden和2块较小的Survivor内存;每次使用Eden和1个Survivor。(Eden:Survivor = 8:1)
  3. 这样内存中整个新生代的内存容量为(80+10=90),保证了只有10%的内存容量的浪费。 但是实际发生垃圾回收时我们无法保证98%对象都是标记死亡的,如果存活的对象的占用的内存多于剩下的10%的容量,这时则需要其他内存(老年代)进行分配担保。


标记 - 整理算法

  1. 复制算法在对象存活率较多时就需要执行较多的复制操作,效率将会降低。
  2. 关键的是,如果不想浪费50%的内存,就需要有额外的内存空间进行分配担保,以应对已使用内存中的对象100%存活的情况。所以老年代一般不会选择此算法(复制算法)。
  3. 根据老年代的特点:提出了标记 - 整理算法。标记过程和标记 - 清理一样。但是后续的步骤:不是对可回收的对象进行清理, 而是让所有存活的对象向一端进行移动。然后清理掉端边界以外的内存
  4. 图解


分代收集算法

  • Java堆:新生代和老年代
  • 新生代:适合复制算法。只需付出复制少量存活对象的成本就可以完成收集。
  • 老年代:(对象的存活率较高,并且没有额外的空间对他进行内存分配)适合使用标记 - 清理或者标记 - 整理算法


HotSpot算法实现

  • 以GC Roots节点找引用链为例(作为GC Roots节点主要是全局性引用常量或类的静态属性或执行上下文栈帧中的本地变量表)
  • 可达性分析对执行时间十分敏感,GC停顿以确保一致性(Stop The World)
  • 准确式GC、OopMap、
相关文章
|
11月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
934 55
|
12月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
923 6
|
存储 分布式计算 监控
阿里云服务器实例经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i详解与选择策略
在阿里云现在的活动中,可选的云服务器实例规格主要有经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例,虽然阿里云在活动中提供了多种不同规格的云服务器实例,以满足不同用户和应用场景的需求。但是有的用户并不清楚他们的性能如何,应该如何选择。本文将详细介绍阿里云服务器中的经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例的性能、适用场景及选择参考,帮助用户根据自身需求做出更加精准的选择。
|
7月前
|
存储 缓存 NoSQL
工作 10 年!Redis 内存淘汰策略 LRU 和传统 LRU 差异,还傻傻分不清
小富带你深入解析Redis内存淘汰机制:LRU与LFU算法原理、实现方式及核心区别。揭秘Redis为何采用“近似LRU”,LFU如何解决频率老化问题,并结合实际场景教你如何选择合适策略,提升缓存命中率。
969 3
|
9月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
595 4
AI代理内存消耗过大?9种优化策略对比分析
|
8月前
|
机器学习/深度学习 监控 安全
解密虚拟化弹性内存:五大核心技术与实施策略
本文深入解析虚拟化环境中实现内存弹性管理的五大核心技术与实施策略。内容涵盖内存架构演进、关键技术原理、性能优化方法及典型问题解决方案,助力提升虚拟机密度与资源利用率。
364 0
|
8月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
276 0
|
弹性计算 安全 数据库
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
作为云计算服务核心组件,虚拟化内存管理直接影响业务系统性能表现。本文详解了内存优化方案与技术实践,助您降低30%资源浪费。
360 0
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
11月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
265 0