对象并不一定都是在堆上分配内存的

转载自 对象并不一定都是在堆上分配内存的

JVM内存分配策略

关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识:

1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。

2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈,或者说是虚拟栈中的局部变量表。

3、栈中存放一些基本类型的变量数据(int/short/long/byte/float/double/Boolean/char)和对象引用。

4、堆中主要存放对象,即通过new关键字创建的对象。

5、数组引用变量是存放在栈内存中,数组元素是存放在堆内存中。

在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述:

但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

这里只是简单提了一句,并没有深入分析,很多人看到这里由于对JIT、逃逸分析等技术不了解,所以也无法真正理解上面这段话的含义。

PS:这里默认大家都了解什么是JIT,不了解的朋友可以先自行Google了解下,或者加入我的知识星球,阅读那篇球友专享文章。

其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做逃逸分析

逃逸分析

逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。

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

例如:

public static StringBuffer craeteStringBuffer(String s1, String s2) {
   StringBuffer sb = new StringBuffer();
   sb.append(s1);
   sb.append(s2);
   return sb;
}

StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

上述代码如果想要StringBuffer sb不逃出方法,可以这样写:

public static String createStringBuffer(String s1, String s2) {
   StringBuffer sb = new StringBuffer();
   sb.append(s1);
   sb.append(s2);
   return sb.toString();
}

不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。

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

一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

上面的关于同步省略的内容,我在《深入理解多线程(五)—— Java虚拟机的锁优化技术》中有介绍过,即锁优化中的锁消除技术,依赖的也是逃逸分析技术。

本文,主要来介绍逃逸分析的第二个用途:将堆分配转化为栈分配。

其实,以上三种优化中,栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点,这里就不展开介绍了。如果大家感兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。

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

 -XX:+DoEscapeAnalysis : 表示开启逃逸分析

 -XX:-DoEscapeAnalysis : 表示关闭逃逸分析 

从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定-XX:-DoEscapeAnalysis

对象的栈上内存分配

我们知道,在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。

我们来看以下代码:

public static void main(String[] args{
   long a1 = System.currentTimeMillis();
   for (int i = 0; i < 1000000; i++) {
       alloc();
   }
   // 查看执行时间
   long a2 = System.currentTimeMillis();
   System.out.println("cost " + (a2 - a1) + " ms");
   // 为了方便查看堆内存中对象个数,线程sleep
   try {
       Thread.sleep(100000);
   } catch (InterruptedException e1) {
       e1.printStackTrace();
   }
}

private static void alloc({
   User user = new User();
}

static class User {

}

其实代码内容很简单,就是使用for循环,在代码中创建100万个User对象。

我们在alloc方法中定义了User对象,但是并没有在方法外部引用他。也就是说,这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后,就可以对其内存分配进行优化。

我们指定以下JVM参数并运行:

-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

在程序打印出 cost XX ms 后,代码运行结束之前,我们使用[jmap][1]命令,来查看下当前堆内存中有多少个User对象:

➜  ~ jps
2809 StackAllocTest
2810 Jps
➜  ~ jmap -histo 2809

num     #instances         #bytes  class name
----------------------------------------------
  1:           524       87282184  [I
  2:       1000000       16000000  StackAllocTest$User
  3:          6806        2093136  [B
  4:          8006        1320872  [C
  5:          4188         100512  java.lang.String
  6:           581          66304  java.lang.Class

从上面的jmap执行结果中我们可以看到,堆中共创建了100万个StackAllocTest$User实例。

在关闭逃避分析的情况下(-XX:-DoEscapeAnalysis),虽然在alloc方法中创建的User对象并没有逃逸到方法外部,但是还是被分配在堆内存中。也就说,如果没有JIT编译器优化,没有逃逸分析技术,正常情况下就应该是这样的。即所有对象都分配到堆内存中。

接下来,我们开启逃逸分析,再来执行下以上代码。

-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

在程序打印出 cost XX ms 后,代码运行结束之前,我们使用jmap命令,来查看下当前堆内存中有多少个User对象:

➜  ~ jps
709
2858 Launcher
2859 StackAllocTest
2860 Jps
➜  ~ jmap -histo 2859

num     #instances         #bytes  class name
----------------------------------------------
  1:           524      101944280  [I
  2:          6806        2093136  [B
  3:         83619        1337904  StackAllocTest$User
  4:          8006        1320872  [C
  5:          4188         100512  java.lang.String
  6:           581          66304  java.lang.Class

从以上打印结果中可以发现,开启了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆内存中只有8万多个StackAllocTest$User对象。也就是说在经过JIT优化之后,堆内存中分配的对象数量,从100万降到了8万。

除了以上通过jmap验证对象个数的方法以外,读者还可以尝试将堆内存调小,然后执行以上代码,根据GC的次数来分析,也能发现,开启了逃逸分析之后,在运行期间,GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配,所以GC次数有了明显的减少。

总结

所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间?

那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329712.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bash on Windows 抢鲜测试 -- 介绍及安装

前言 微软在上周的Windows BUILD大会上宣布&#xff0c;WIN10将引入原生Bash&#xff0c;并将很快在技术预览版中推出。 如此一来&#xff0c;windows的命令行工具就不再只有cmd和powershell了&#xff0c;我们可以还可以使用bash。 今天&#xff08;2016/4/8&#xff09;收到了…

jvm的client与server工作模式

【README】 JVM Server模式与client模式启动&#xff0c;最主要的差别在于&#xff1a; -Server模式启动时&#xff0c;速度较慢&#xff0c;但是一旦运行起来后&#xff0c;性能将会有很大的提升. 原因是:当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译…

python重定向到socket_python套接字流重定向实例汇总

#!/usr/bin/env python3"""测试socket-stream 重定向模式"""import sys,os,timefrom multiprocessing import Processfrom socket import *def initListenerSocket(port50008,host):"""初始化在服务器模式下调用者用于监听连接的…

Spring Boot的自动化配置原理

转载自 Spring Boot的自动化配置原理 随着Ruby、Groovy等动态语言的流行&#xff0c;相比较之下Java的开发显得格外笨重。繁多的配置、低下的开发效率、复杂的部署流程以及第三方技术集成难度大等问题一直被人们所诟病。随着Spring家族中的新星Spring Boot的诞生&#xff0c;这…

复盘Build 2016:不要错过微软给.NET开发者的这些福利

本文作者陈计节&#xff0c;ThoughtWorks 高级咨询师。多年的跨平台 .NET 开发者&#xff0c;全栈工程师&#xff0c;技术布道师。擅长互联网应用程序的设计、开发和运维等工作。 近年来&#xff0c;微软坚持为社区提供更多灵活性&#xff0c;并以更开放的思路重构其已有平台&a…

java异步线程内存可见性实验

【README】 本文演示了内存可见性的场景&#xff0c;以及解决方法&#xff1b; 相关定义如下&#xff08;转自java并发编程实战&#xff0c;一本好书&#xff0c;强烈推荐&#xff09;&#xff1a; 内存可见性&#xff1a;一个线程修改了对象状态后&#xff0c; 其他线程可以…

springboot 单例_如何实现一个单例及优化

前言 社长,一个爱学习,爱分享的程序猿,始终相信,付出总会有回报的。 知识改变命运,学习成就未来。爱拼才会赢! 程序猿学社的GitHub,已整理成相关技术专刊,欢迎 Star:。 https:// github.com/ITfqyd/cxyxs 社长,4年api搬运工程师,之前做的都是一些框架的搬运工作,做的时间…

乐观锁的一种实现方式——CAS

转载自 乐观锁的一种实现方式——CAS在深入理解乐观锁与悲观锁一文中我们介绍过锁。本文在这篇文章的基础上&#xff0c;深入分析一下乐观锁的实现机制&#xff0c;介绍什么是CAS、CAS的应用以及CAS存在的问题等。线程安全 众所周知&#xff0c;Java是多线程的。但是&#xff0…

熬夜并不值得程序员炫耀

许多程序嘴上经常挂着每天加班到凌晨三点的论调&#xff0c;但事实上这没什么值得炫耀的&#xff0c;加班通常都是效率低的代名词。 放弃睡眠就像是高利贷借款。没错&#xff0c;看上去你是得到了额外的时间&#xff0c;但你想得太乐观了&#xff0c;你知道代价是什么吗&#x…

前端wxml取后台js变量值_这些鲜为人知的前端冷知识,你都GET了吗?

背景最近公司项目不多&#xff0c;比较清闲&#xff0c;划水摸鱼混迹于各大技术博客平台&#xff0c;瞬间又GET了好多前端技能&#xff0c;一些属于技巧&#xff0c;一些则是闻所未闻的冷知识&#xff0c;一时间还消化不过来&#xff0c;不由的发出一声感叹&#xff01;前端可真…

(转-这篇文章非常棒) Thread的中断机制(interrupt)

转自&#xff1a; Thread的中断机制(interrupt)-这篇文章非常棒 Thread的中断机制(interrupt) - 寂静沙滩 - 博客园先看收集了别人的文章&#xff0c;全面的了解下java的中断&#xff1a;中断线程线程的thread.interrupt()方法是中断线程&#xff0c;将会设置该线程的中断状态…

深入理解乐观锁与悲观锁

转载自 [初级]深入理解乐观锁与悲观锁在数据库的锁机制中介绍过&#xff0c;数据库管理系统&#xff08;DBMS&#xff09;中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。 乐观并发控制(乐观锁)和悲观并发控制&a…

Z.ExtensionMethods 一个强大的开源扩展库

今天有意的在博客园里面搜索了一下 Z.ExtensionMethods 这个扩展类库&#xff0c;确发现只搜到跟这个真正相关的才两篇博文而已&#xff0c;我都点进去看了一下&#xff0c;也都只是提到而已&#xff0c;没有专门介绍&#xff0c;才引起我写这篇文档。 一&#xff0e; Z.E…

python isodd奇偶_Python这些位运算的妙用,绝对让你大开眼界

位运算的性能大家想必是清楚的&#xff0c;效率绝对高。相信爱好源码的同学&#xff0c;在学习阅读源码的过程中会发现不少源码使用了位运算。但是为啥在实际编程过程中应用少呢&#xff1f;想必最大的原因&#xff0c;是较为难懂。不过&#xff0c;在面试的过程中&#xff0c;…

java线程中断

【README】 本文po出了不同场景下线程中断的不同开发方式&#xff0c;包括阻塞&#xff0c;非阻塞&#xff0c;io阻塞线程等&#xff1b; 本文部分内容转自&#xff1a;这篇博文写的非常好 Thread的中断机制(interrupt) - 寂静沙滩 - 博客园先看收集了别人的文章&#xff0c…

WEB API系列(一):WEB API的适用场景、第一个实例

在我前一篇博客《WebAPI前置知识:HTTP与RestfulAPI》中已经给各位简单介绍了HTTP协议与RestFul API的关系&#xff0c;以及一些基本的HTTP协议知识&#xff0c;在这些知识的铺垫下&#xff0c;今天&#xff0c;我们一起来讨论一下WEB API的适用场景&#xff0c;然后写我们第一个…

rpa文件怎么提取内容_怎么编辑pdf文件内容?有什么软件可以编辑pdf文件吗?

怎么编辑pdf文件内容&#xff1f;我是2020年大学新生&#xff0c;选择学习的专业是财务管理。为了更快、更深入地了解更多专业知识&#xff0c;我上网查找、下载了很多相关资料。不过&#xff0c;从网上保存下来的资料文件大多是pdf格式的&#xff0c;想要编辑它还得先转word。…

Future取消线程执行

【README】 本文总结于 《java并发编程实战》 page121&#xff0c;非常棒的一本书&#xff1b; 【1】Future 1&#xff0c;介绍&#xff1a;future 用于管理任务的生命周期&#xff0c;处理异常&#xff0c;以及实现取消&#xff1b; 2&#xff0c;future.cancel() 取消方法…

Nancy之结合tinyfox给我们的应用提供简单的数据服务

说到提供数据服务给我们的一些应用&#xff0c;估计用的最多的也就是json和xml这两种数据格式 实现的方法也是多种多样&#xff0c;web api,mvc的jsonresult和contentresult...等等 本文是结合Nancy、TInyFox、Owin等来实现的 一、前提工作 新建一个空的web应用程序 添加相应的…

javaweb调用python算法_请教怎么用java远程调用python? 多谢

请问如何用java远程调用python? 谢谢&#xff01;本帖最后由 blackkettle 于 2015-05-07 13:00:41 编辑比如有一台机器 A上安装了python, 另一台机器B要用java 调用A 上的python的某个函数&#xff0c;输入数据在B机器上&#xff0c;所有的计算在A机器完成&#xff0c;结果返回…