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. win7重装系统经验总结报告(2013年6月29日凌晨1:45)

    win7重装系统经验总结报告(2013年6月29日凌晨1:45) 步骤: 1.考虑被重做的电脑是否有重要文件在C盘.有则转移到D盘等非系统盘. 2.看该电脑是2GB内存还是4GB以上内存. 3.准备好 ...

  2. 函数基础(一)(day10整理)

    目录 昨日内容 文件的基本应用 什么是文件 操作文件的流程 打开文件的三种模式 文件打开的两种方式 绝对路径和相对路径 绝对路径 相对路径 with管理文件上下文 文件的高级应用 新的打开文件的模式 ...

  3. SpringBoot与MybatisPlus整合之公用字段填充(十一)

    在实际开发中,适合用于记录创建人修改人 pom.xml <dependencies> <dependency> <groupId>org.springframewo ...

  4. redis操作总结

    一.redis 终端操作命令: 1.redis-server.exe redis.windows.conf #指定配置文件启动 2.redis-cli #链接redis数据库 3.select 1 # ...

  5. 星级评分条(RatingBar)的功能与用法

    星级评分条与拖动条有相同的父类:AbsSeekBar,因此它们十分相似.实际上星际评分条与拖动条的用法.功能都十分接近:它们都允许用户通过拖动来改变进度.RatingBar与SeekBar的最大区别在 ...

  6. 全栈项目|小书架|服务器开发-Koa2 全局异常处理

    什么是异常 做开发的基本都知道异常,像Android开发中常见的ANR异常.空指针异常,服务器开发中经常遇到的异常404,500异常,还有一些其他常见的异常,具体可见HTTP状态码. 基本上这些异常可 ...

  7. 如何进行kubernetes问题的排障

    排障的前置条件 k8s的成熟度很高,伴随着整个项目的扩增,以及新功能和新流程的不断引入,也伴随这产生了一些问题.虽然自动化测试可以排除掉大部分,但是一些复杂流程以及极端情况却很难做到bug的完全覆盖. ...

  8. mysql多种备份与恢复方式一

    基于mysql社区版5.7,严重声明:本文中图方便直接写入了-p数据库密码,在生产环境中如果指定了一定要使用history -C清空历史命令记录哦,所有博客作者原创纯手打,转载一定要注明本博客链接,感 ...

  9. CSPS模拟 65

    cbx又A两题%%% T1比较水,只需要想到lcm以内的数都只能被唯一凑出就可以. T2比较shi,毕竟std复杂度都不正确的题是不能称之为完整的题的. 那么再感受一下T3满满的恶意 起点已经固定了, ...

  10. WeihanLi.Npoi 近期更新

    WeihanLi.Npoi 近期更新 Intro 最近对我的 NPOI 扩展做了一些改变,一方面提高性能,一方面修复bug,增加一些新的功能来让它更加好用,前几天发布了 1.5.0 版本,下面来介绍一 ...