Java程序在内存中运行详解

Java语言是一门编译型语言,需要将编写的源代码(.java文件)编译之后(.class字节码文件),通过 jvm 才能正常的执行,下面的内容记录了一个程序从编写到执行整个过程在内存中是怎么一个变的。

一、JVM的内存分布

先了解下 JVM 的内存分布,因为Java程序想要运行,就要依靠 JVM,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM 实现了Java 的平台无关性,由此可见JVM的重要性。所以在学习 Java 内存分配原理的时候一定要牢记这一切都是在 JVM 中进行的,JVM 是内存分配原理的基础与前提。

1.jvm内存分布图

从图片中看,一共分为了5大区域,分别是:方法区、堆、栈、本地方法区、程序计数器。

这里我们主要了解下 方法区堆、 栈、这三个区域。

2.方法区:

方法区是一块所有线程共享的内存区域。
保存系统的类信息,比如,类的字段,方法,常量池等等。

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误

jdk1.6和jdk1.7方法区可以理解为永久区。

jdk1.8已经将方法区取消,替代的是元数据区。

jdk1.8的元数据区可以使用参数-XX:MaxMetaspaceSzie设定大小,这是一块堆外的直接内存,与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽可用系统内存。

3.堆:

用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。在堆中只会存储成员方法的地址,在调用的时候,根据地址去方法区中执行对应的成员方法。

4. 栈:

栈生命周期与线程相同。启动一个线程,程序调用函数,栈帧被压入栈中,函数调用结束,相应的是栈帧的出栈。

栈帧由局部变量表,操作数栈,帧数据区组成。

局部变量表:存放的是函数的入参,以及局部变量。

操作数栈:存放调用过程中的计算结果的临时存放区域。

帧数据区:存放的是异常处理表和函数的返回,访问常量池的指针。

举个例子,线程执行进入方法A,则会创建栈帧入栈,A方法调用了B方法,B栈帧入栈,B方法中调用C方法,C创建了栈帧压入栈中,接下来是D入栈

反过来,D方法执行完,栈帧出栈,接着是C、B、A。

二、程序执行的过程

从上图我们看到了一个程序在内存中执行的过程。

上图的执行流程:

1.从 disk 中将 MainApp.class 加载到 jvm 的方法区中。

2.执行 main 方法,将该 main 方法中包含的变量和函数,压到栈中。

3.开始执行 main 方法中的指令,创建一个 animal 对象, 将 new 出来的 animal 对象存储到堆中,animal 引用指向堆中的 animal 对象,堆中的 animal 对象指向方法区中的 Animal 类。

4.继续执行 main 方法中的指令,调用 animal 对象中的 printName() 方法,这时 animal 应用调用 animal 对象, animal 对象找到方法区的 Animal 类中的 printName() 字节码信息,根据该描述信息,开始执行 printName方法。

三、只有一个对象时的内存图

从左侧我们看到有两个类,按照Java程序的执行流程,会把这两个类编译成 .class 文件,即图中最右边的 Phone.class he Demo01PhoneOne.class。

首先程序开始执行是从 main() 方法开始,这个时候会把 main() 方法压到栈中,main() 方法中的第一句代码是先创建一个 Phone 对象,当我们 new 一个对象时,会把 new 出来的对象放到堆中,相对应的给这个对象分配一个地址值,在栈中会产生一个实例 one 会指向这个地址,可以看到堆中的对象包含了自身的成员变量和成员方法的引用。

接着继续执行下面的代码,直接打印对象的属性值,由于对象属性没有进行赋值,所以输出的都是对应数据类型的默认值。 继续下面的操作,就是给对象的属性进行赋值,由于 one 是指向了对象,所以直接可以进行操作,这时在堆中的属性值就会被赋予对应的值了。再次打印的时候就会打印出对应的值。

再到后面,继续调用了对象的成员方法,这个时候需要先在堆中找到这个成员方法的应用,然后找到方法区中将对应的代码压到栈中,继续执行。调用方法会传入对应的参数,也是放到栈中的,执行完这个方法之后,压到栈中的这一部分代码就会出栈,直到 main() 方法中所有的代码执行完,栈中的内容也就全部消失,内存也就随之释放。

四、两个对象使用同一个方法的内存图

这里和上面不同的是创建了两个对象,但是操作的内容还是和上面一样的。唯一区别就是在调用成员方法时,调用的是同一个。

刚开始也说到了,同一个类创建多个对象时,他们是各自拥有自己的成员变量了,但是应用的成员方法却是同一个。

从图中我们就可以看出,给两个对象进行赋值时,是会打印出不同的值的。调用方法时,使用的还是同一个方法。

五、两个引用指向同一个对象的内存图

当我理解了前面两个图后,看到这里应该也不会有什么难度了,这里我们只 new 了一个对象,但是却有两个实例,从图中也可以看到堆里面只有一个对象。

看到图最左边,我们把 one 实例直接就赋值给了 two, 其实就是把 one 的地址值赋给了 two, 这时 two 也就和one 指向了同一个对象。这时去改变对象中的值,就会把 one 原来赋的值直接覆盖掉。最终打印的就是 two 实例赋的值了。

六、使用对象类型作为方法参数的内存图

使用对象类型作为方法的参数,在传递的过程中,实际上传递的是引用,即对象的地址值。当我们在另外一个方法中改变了这个对象的属性时,对象原来的值就会被覆盖。

七、对象类型作为方法返回值得内存图

对象类型作为返回值也是一样的道理,返回的实际是对象的地址值。

八、总结

  1. 分清什么是实例什么是对象。Class a= new Class(); 此时 a 叫实例,而不能说 a 是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

  2. 栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

  3. 以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

  4. 类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

  5. 对象类型作为方法的参数或者方法的返回值时,传递的都是对象的地址值。再其他地方修改这个对象的属性值时,原有的值就会被覆盖掉。

参考文章:

https://blog.csdn.net/yangyuankp/article/details/7651251

http://www.yq1012.com/jichu/4540.html

Java程序在内存中运行详解的更多相关文章

  1. Java连接数据库的4中方式详解

    Java连接数据库的方式有多种:根据所需要的不同数据库驱动分,分为四种: 1:1类驱动.这就是JDBC-ODBC桥的方式. 但这种方式不适合程序的重用与维护,不推荐使用.需要数据库的ODBC驱动. 2 ...

  2. java程序启动参数-D含义详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt407 java程序启动参数 -D是用来做什么的呢?去查询了一下官方解释: S ...

  3. Java环境配置与编译运行详解

    这篇文章主要为大家详细介绍了Java环境配置与编译运行的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 一.开篇 通过对之前Java之路的了解之后,相信初学者们都对Java有了一个比较深印 ...

  4. 【转】 java中HashMap详解

    原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...

  5. java中HashMap详解(转)

    java中HashMap详解 博客分类: JavaSE Java算法JDK编程生活       HashMap 和 HashSet 是 Java Collection Framework 的两个重要成 ...

  6. java集合(2)- java中HashMap详解

    java中HashMap详解 基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 H ...

  7. Java内存模型(JMM)详解

    在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...

  8. 【转帖】windows命令行中java和javac、javap使用详解(java编译命令)

    windows命令行中java和javac.javap使用详解(java编译命令) 更新时间:2014年03月23日 11:53:15   作者:    我要评论 http://www.jb51.ne ...

  9. java线程池的使用与详解

    java线程池的使用与详解 [转载]本文转载自两篇博文:  1.Java并发编程:线程池的使用:http://www.cnblogs.com/dolphin0520/p/3932921.html   ...

随机推荐

  1. Process类调用exe,返回值以及参数空格问题

    (方法一)返回值为int fileName为调用的exe路径,入口参数为para,其中多个参数用空格分开,当D:/DD.exe返回值为int类型时. Process p = new Process() ...

  2. seq2seq+attention解读

    1什么是注意力机制? Attention是一种用于提升Encoder + Decoder模型的效果的机制. 2.Attention Mechanism原理 要介绍Attention Mechanism ...

  3. 关于高淇JAVA中SORM总结学习笔记详细个人解释

    代码来源于高淇JAVA教学视频 谢谢高淇老师的教学. 因为自己在学习的过程中发现了很多困难点,总结下希望对自己接下来学框架提升.给像我一样得初学者方便. SORM框架是一个简单的ORM,关系对象映射, ...

  4. 数据后台管理(五)AOP日志

    为了增加数据的安全性,在数据管理的过程中,我们需要将操作者访问时间,操作者的名称,访问的IP,访问资源的URL,执行时长,访问方法记录下来存储到数据库中,并可以通过页面查看. 1.将日志信息存储到数据 ...

  5. 超简单让.NET Core开发者快速拥有CI/CD的能力-Docker版本

    超简单让.NET Core开发者快速拥有CI/CD的能力-Docker版本 前言 上一篇自动化测试,全面且详细的介绍了从零开始到发布版本的步骤,这是传统的方式,本次为大家带来的是如何在5分钟内使用上d ...

  6. 洛谷P5522 【[yLOI2019] 棠梨煎雪】

    区间操作考虑用线段树维护. 建\(n*2\)棵线段树,前\(n\)棵线段树维护每个串的第i位是否是0. 后\(n\)棵线段树维护每个串的第i位是否是1. 如果是问号的话,直接跳过就好(通过1和0能看出 ...

  7. nginx篇最初级用法之三种虚拟主机基于域名\基于端口\基于IP地址端口的虚拟主机

    在nginx中虚拟主机的类型与apache一样也有三种 1.基于域名的虚拟主机 2.基于端口的虚拟主机 3.基于IP地址端口的虚拟主机 在nginx配置文件中每一个server为一个虚拟主机如果需要多 ...

  8. 2019.NET Conf China(中国.NET开发者峰会)活动全纪录:.NET技术之崛起,已势不可挡

    一.微软走向开放与.NET Core的诞生 当今时代,气象更新,技术飞速发展. 当今时代,开发者大概是最优秀的群体.每一位开发者,无不奋勇向前,努力追寻时代的步伐,以大无畏的精神迎接挑战,紧跟大时代成 ...

  9. Tomcat+nginx+Keepalived部署实现集群

    Tomcat+nginx+Keepalived部署实现集群 环境说明: 系统:Centos-7 主机:Centos-7 x3 IP地址: 服务器1(192.168.10.102/24) 服务器2(19 ...

  10. PHP yaf显示错误提示

    PHP yaf显示错误提示 1就是配置文件的那个错误 <pre>error_reporting(E_ALL);</pre> 2init.php文件的<pre>fun ...