探究跨代引用和逃逸分析如何提升程序性能!

📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、优快云博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。

📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。🎥有从0到1的高并发项目经验,利用弹性伸缩、负载均衡、报警任务、自启动脚本,最高压测过200台机器,有着丰富的项目调优经验。

📙经过多年在优快云创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!

以梦为马,不负韶华

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。

优快云
探究跨代引用和逃逸分析如何提升程序性能!

🍊 跨代引用

在Java虚拟机中,跨代引用指的是老年代中的对象引用了年轻代中的对象。当进行年轻代的垃圾回收时,需要扫描老年代中所有引用年轻代的对象,在老年代里面做大量的遍历和扫描操作,这是非常消耗性能的。为了优化这个问题,JVM引入了记忆集(Card Table)的抽象数据结构。

记忆集记录了从非收集区域指向收集区域的一个指针集合,这个指针集合是由一组连续的内存卡片(Card)组成的。每个内存卡片的大小是2^18个字节,可以理解为一个内存页面。记忆集能够记录指向年轻代的内存卡片中所有的指针,将这些指针标记为"dirty"。在minor gc时,只需要扫描"dirty"的内存卡片,而不是扫描老年代中所有引用年轻代的对象。

思路比较清晰,现在来看一下这个问题的示例代码:

public class CrossGenerationReferenceExample {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        Object obj = new Object();
        for (int i = 0; i < 1000000; i++) {
            list.add(obj);
        }
    }
}

这个示例代码中的问题在于,对象obj在年轻代中创建,但是被放到了老年代中的ArrayList里面。这个时候,当进行年轻代的垃圾回收时,就需要扫描老年代中所有引用年轻代的对象,而且这个ArrayList还是一个非常大的对象,扫描的效率非常低下。

现在来看一下使用记忆集优化之后的代码:

public class CrossGenerationReferenceExample {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        Object obj = new Object();
        for (int i = 0; i < 1000000; i++) {
            list.add(obj);
            if (i % 20000 == 0) {
                System.gc();
            }
        }
    }
}

在这个示例代码中,每添加20000个对象就手动执行一次垃圾回收操作。这个操作会强制触发记忆集的更新,即将内存卡片中的指针标记为"dirty"。这样,在下一次垃圾回收时,就只需要扫描"dirty"的内存卡片,提高了垃圾回收的效率。

🍊 逃逸分析

逃逸分析是Java虚拟机优化的重要手段之一,它可以通过静态分析来确定对象的动态作用域,判断一个对象是否会“逃逸”,即被其他方法或线程引用。如果对象不会逃逸,则可以将对象的创建和销毁都放在栈上进行,从而减少在堆上进行内存分配和回收的开销。

逃逸分析有三种程度:从不逃逸、方法逃逸和线程逃逸。这三个由低到高表示不同逃逸的程度。

🎉 栈上分配

在Java虚拟机中,堆中的对象对于所有线程都是可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。虚拟机的垃圾回收子系统会回收堆中不再使用的对象,但是回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。

如果确定一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例是很大的,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁,垃圾回收子系统的压力将会下降很多。

栈上分配可以支持方法逃逸,但不能支持线程逃逸。

下面是一个栈上分配的示例代码:

public static void main(String[] args) {
    for (int i = 0; i < 1000000; i++) {
        Point p = new Point(1, 2);
        p.setX(i);
        p.setY(i);
    }
}

class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
}

在这个示例代码中,每次循环都创建一个Point对象,在Point对象的构造函数中将xy初始化。Point对象不会逃逸出方法之外,因此可以将它们分配在栈上。

🎉 标量替换

一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。

一个数据可以继续分解,那它就被称为聚合量。Java中的对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问,这个过程就称为标量替换。

假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么程序真正执行的时候将可能不去创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来代替。

将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,很大机会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。标量替换可以视作栈上分配的一种特例,实现更简单(不用考虑整个对象完整结构的分配),但对逃逸程度的要求更高,它不允许对象逃逸出方法范围内。

下面是一个标量替换的示例代码:

public static void main(String[] args) {
    for (int i = 0; i < 1000000; i++) {
        String s = new String("abc" + i);
        int length = s.length();
    }
}

在这个示例代码中,每次循环都创建一个String对象,并取出它的长度。逃逸分析能够证明String对象不会逃逸出方法之外,因此可以将对象的成员变量拆分为一个char数组和一个offset整型变量。在Java虚拟机内部,会把char数组和offset整型变量分别分配在栈上,而不需要在堆上分配String对象。

总之,逃逸分析是一种非常重要的JVM优化手段,能够让JVM能够更好地利用现代计算机的硬件资源,提高程序的性能。通过栈上分配和标量替换等技术,能够在一定程度上减少对堆内存的使用,从而让垃圾回收的效率更高,进而让程序能够更好地使用计算机的内存和CPU资源。

优快云

🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

📥博主的人生感悟和目标

探寻内心世界,博主分享人生感悟与未来目标

  • 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本身是一个很普通程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
  • 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
  • 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
  • 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。
根据原作 https://pan.quark.cn/s/459657bcfd45 的源码改编 Classic-ML-Methods-Algo 引言 建立这个项目,是为了梳理总结传统机器学习(Machine Learning)方法(methods)或者算法(algo),各位同仁相互学习交流. 现在的深度学习本质上来自于传统的神经网络模型,很大程度上是传统机器学习的延续,同时也在不少时候需要结合传统方法来实现. 任何机器学习方法基本的流程结构都是通用的;使用的评价方法也基本通用;使用的一些数学知识也是通用的. 本文在梳理传统机器学习方法算法的同时也会顺便补充这些流程,数学上的知识以供参考. 机器学习 机器学习是人工智能(Artificial Intelligence)的一个分支,也是实现人工智能最重要的手段.区别于传统的基于规则(rule-based)的算法,机器学习可以从数据中获取知识,从而实现规定的任务[Ian Goodfellow and Yoshua Bengio and Aaron Courville的Deep Learning].这些知识可以分为四种: 总结(summarization) 预测(prediction) 估计(estimation) 假想验证(hypothesis testing) 机器学习主要关心的是预测[Varian在Big Data : New Tricks for Econometrics],预测的可以是连续性的输出变量,分类,聚类或者物品之间的有趣关联. 机器学习分类 根据数据配置(setting,是否有标签,可以是连续的也可以是离散的)任务目标,我们可以将机器学习方法分为四种: 无监督(unsupervised) 训练数据没有给定...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值