当前位置: 首页 > news >正文

Java对象逃逸

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载

目录

  • 一、导读
  • 二、概览
  • 三、相关知识
    • 3.1 逃逸分析
    • 3.2 对象逃逸状态
    • 3.3 Java中的对象都是在堆中分配吗?说明为什么!
  • 四、 推荐阅读

在这里插入图片描述

一、导读

我们继续总结学习Java基础知识,温故知新。

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。

二、概览

Java对象逃逸指的是一个对象在其应该被限制访问的范围之外被引用或访问的情况,简单解释就是我有一个方法,在方法内创建了一个对象,但是这个对象传递到其他地方了。
在Java中,对象一般在包含它们的方法中创建和使用,当方法结束时,这些对象会被回收。然而,当对象在方法中被引用或传递到其他方法中时,就会发生对象逃逸。

我们举例

    这种写法直接返回的是对象,用处就是被别的变量所引用,会造成对象逃逸,从而增加了GC的压力。public StringBuilder getSb(){StringBuilder sb = new StringBuilder("");return sb;}不如改成下面这样public String getSb1(){StringBuilder sb = new StringBuilder("");return sb.toString();}

第一段代码中的sb就逃逸了,而第二段代码中的sb就没有逃逸。

三、相关知识

在这之前,我们要先了解一些jvm的基本知识。

Java运行时数据区(Runtime Data Area)是指在Java程序执行期间,Java虚拟机所管理的诸多内存区域(分别用于存储不同的数据),如上图所示,包含了以下几个部分:

  • 堆区 (主要用于存储对象实例、数组)
  • 栈区 (主要存放java方法、局部变量、操作数栈、动态链接、方法出口、基本类型的变量数据、对象的引用等)
  • 方法区 (主要用于存储类型信息、常量、静态变量、即时编译代码等)
  • 程序计数器

一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,
第一段是把.java文件转换成.class文件。
第二段是把.class转换成机器指令的过程,JVM 通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译,后来为了解决效率问题,引入了 JIT(即时编译) 技术。

JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做逃逸分析。通过逃逸分析,Java Hotspot编译器能够
分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。逃逸分析的基本行为就是分析对象动态作用域

public static StringBuffer craeteStringBuffer(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb;
}sb是一个方法内部变量,上述代码中直接将sb返回,这样这个sb 有可能被其他方法所改变,这样它的作用域就不只是在方法内部

3.1 逃逸分析

逃逸分析(Escape Analysis)简单来讲就是,Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术。

在Java代码运行时,通过JVM参数可指定是否开启逃逸分析,

  • -XX:+DoEscapeAnalysis : 开启逃逸分析. 从jdk 1.7开始已经默认开始逃逸分析
  • -XX:-DoEscapeAnalysis : 关闭逃逸分析 。
  • -XX:+PrintEscapeAnalysis : 显示分析结果

使用逃逸分析,编译器可以对代码做如下优化:

  • 同步省略 (锁消除)
public void f() {Object hollis = new Object();synchronized(hollis) {System.out.println(hollis);}
}优化后变成 
public void f() {Object hollis = new Object();System.out.println(hollis);
}
  • 分离对象或标量替换
  • 将堆分配转化为栈分配
    当对象没有发生逃逸时,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配,该对象就可以通过标量替换分解成成员标量分配在栈内存中,
    和方法的生命周期一致,随着栈帧出栈时销毁,减少了 GC 压力,提高了应用程序性能

3.2 对象逃逸状态

1、全局逃逸(GlobalEscape)
即一个对象的作用范围逃出了当前方法或者当前线程,有以下几种场景:

  • 对象是一个静态变量
  • 对象是一个已经发生逃逸的对象
  • 对象作为当前方法的返回值

2、参数逃逸(ArgEscape)
即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。

对象逃逸可能会导致以下问题:

  1. 安全问题:对象逃逸可以使对象暴露给不受信任的代码,可能导致数据泄露或被篡改。
  2. 性能问题:对象逃逸可能导致对象的生命周期变得不可预测,增加垃圾回收的负担,降低系统性能。
  3. 并发问题:对象逃逸可能导致多个线程同时访问同一个对象,造成线程安全问题。

为了解决对象逃逸问题,可以采取以下措施:

  1. 限制对象的访问范围:将对象的作用域限制在方法内部,避免在方法外部引用或传递对象。
  2. 使用局部变量代替成员变量:将对象定义为方法内的局部变量而不是成员变量,这样可以避免对象在方法外部被引用。
  3. 使用不可变对象:如果对象是不可变的,那么即使发生逃逸,也不会出现安全和并发问题。
  4. 使用线程安全的数据结构:如果对象需要在多个线程之间共享,应该使用线程安全的数据结构或采用同步控制机制来保证并发安全性。

通过避免对象逃逸,可以提高代码的安全性、性能和并发性能。

3.3 Java中的对象都是在堆中分配吗?说明为什么!

对象和数组并不一定都在堆上分配内存的,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,
那么有可能堆内存分配会被优化成栈内存分配,
JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。

举个栗子:

先定义一个类 XYZclass XYZ {int i;
}在定义一个方法 abc(),方法内使用了XYZ类,但是并没有外部引用,也就说这个对象不会发生逃逸。public void abc() {XYZ xyz = new XYZ();
}最后再定义一个for循环来调用abc()方法,假设我们在代码中创建100万个XYZ对象,for (int i = 0; i < 1000000; i++) {abc();}

假设我们先关闭逃逸分析,在代码结束前使用[jmap][1]命令,来查看下当前堆内存中有100万个XYZ对象.
-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

接下来,我们开启逃逸分析,再来执行下以上代码,使用jmap命令,来查看下当前堆内存中有几万个XYZ对象,不是一个量级。
堆内存中分配的对象数量大量减少,
-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

四、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

ddd

相关文章:

  • 【学生成绩管理】数据库示例数据(MySQL代码)
  • 第十三章 : Spring Boot 日志记录脱敏
  • 【Python 训练营】N_3 生成互不相同且不重复的数字
  • 核药供应链创新:远大医药策略与明道云实践
  • 认识前端包常用包管理工具(npm、cnpm、pnpm、nvm、yarn)
  • 家用小型洗衣机哪款性价比高?口碑最好迷你洗衣机排行榜
  • 最新AIGC创作系统ChatGPT网站源码,Midjourney绘画系统,支持GPT-4图片对话能力(上传图片并识图理解对话),支持DALL-E3文生图
  • gitlab 12升级14(解决各种报错问题)
  • 数字图像处理(实践篇)一 将图像中的指定目标用bBox框起来吧!
  • Maven项目下详细的SSM整合流程
  • 喜报|AIRLOOK荣获“创客北京2023”创新创业大赛企业组三等奖
  • 接口测试基本流程
  • Spring Boot 升级3.x 指南
  • 搭建SRS视频服务器
  • 梨花声音教育,美食视频配音再次挑战味蕾
  • [译]如何构建服务器端web组件,为何要构建?
  • Java教程_软件开发基础
  • Mac转Windows的拯救指南
  • MySQL的数据类型
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • 阿里研究院入选中国企业智库系统影响力榜
  • 初识 beanstalkd
  • 从PHP迁移至Golang - 基础篇
  • 今年的LC3大会没了?
  • 聊聊redis的数据结构的应用
  • 一个SAP顾问在美国的这些年
  • 从如何停掉 Promise 链说起
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • #《AI中文版》V3 第 1 章 概述
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (超详细)语音信号处理之特征提取
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (数据结构)顺序表的定义
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (小白学Java)Java简介和基本配置
  • (一)Linux+Windows下安装ffmpeg
  • (转)ABI是什么
  • (转)memcache、redis缓存
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET中两种OCR方式对比
  • @angular/cli项目构建--Dynamic.Form
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @SentinelResource详解
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [100天算法】-不同路径 III(day 73)
  • [2015][note]基于薄向列液晶层的可调谐THz fishnet超材料快速开关——
  • [acm算法学习] 后缀数组SA
  • [Angular] 笔记 20:NgContent