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. Spark执行流程(转)

       原文地址:http://blog.jobbole.com/102645/     我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程.根据你使 ...

  2. javascript domAPI整理

    对一般兼容性和特点做了标识(主要是ie8+) 节点类型 节点类型 节点值 标签节点(Element) 1 属性节点(Attr) 2 文本节点(Text) 3 CDATA节点(CDATASetion) ...

  3. vue的数据双向绑定

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  4. fenby C语言 P20

    循环停止 break 立刻跳出不再循环 continue立刻跳出循环从下一次循环继续执行 #include <stdio.h> int main(){ int i; for(i=1;i&l ...

  5. WinDag基础1

    建立调试会话 用户层调试会话的建立 直接创建进程并调试 附加到已经打开的进程 侵入式附加:接管正在运行的进程,可以进行调试 非侵入式附加:只能读取进程信息,不能接收目标进程的调试事件 通常情况下一个程 ...

  6. Java开发中的23中设计模式详解(一)工厂方法模式和抽象工厂模式

    一.设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接 ...

  7. Phpstudy隐藏后门

    Phpstudy隐藏后门 1.事件背景 Phpstudy软件是国内的一款免费的PHP调试环境的程序集成包,通过集成Apache.PHP.MySQL.phpMyAdmin.ZendOptimizer多款 ...

  8. MySQL的统计信息学习总结

    统计信息概念 MySQL统计信息是指数据库通过采样.统计出来的表.索引的相关信息,例如,表的记录数.聚集索引page个数.字段的Cardinality.....MySQL在生成执行计划时,需要根据索引 ...

  9. Elasticsearch6.x集群部署

    一.准备阶段 三台ubuntu系统机器 ip 计算机名 192.168.2.132 master 192.168.2.133 slave1 192.168.2.134 slave2 下载: jdk-8 ...

  10. Vue项目中执行npm run dev 不报错也不显示点击的地址链接

    问题描述: 输入npm run dev 没有报错也没有显示可以点击的地址链接,如下图: 解决方法: 具体配置: autoOpenBrowser默认为false,改为true.重新 npm run de ...