java程序在运行过程各个内部结构的作用

2024-06-12 06:12

本文主要是介绍java程序在运行过程各个内部结构的作用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一:内部结构

一个进程对应一个jvm实例,一个运行时数据区,又包含多个线程,这些线程共享了方法区和堆,每个线程包含了程序计数器、本地方法栈和虚拟机栈接下来我们通过一个示意图介绍一下这个空间。

如图所示,当一个helloword程序编译成为可以运行的二进制编码程序helloword.class程序的时候,运行二进制文件,整个进程会放到一个本地电脑的内存环境中去,其中,一个程序就是一个JVM实例,代码中包含定义方法的方法区域,开辟空间的堆空间区域,以及控制好程序的运行走向的程序计数器,以及方法调用时候用到的本地方法栈,虚拟机栈等,在代码运行的过程中我们知道代码会产生很多的垃圾,这些垃圾是需要JVM中的垃圾回收器去回收的她不像C或CPP那样手动的垃圾清除,在JAVA中一个JVM空间会有一个GC垃圾回收器,通过各种有效的算法帮我们实现垃圾的自动清除,后面我会介绍GC如何实现垃圾回收机制的。

二:程序计数器

程序计数器是什么?相信大家第一次听到这个话题的时候应该很敏感,其实博主是嵌入式转JAVA的所以对于底层还是颇有了解的。

  • 程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号),简单来说就是记住我现在是哪个线程在具体的运行,保存一下

  • 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。(这里我来说明一下如下图所示,线程轮转简单来说就是时间片轮转,因为对于单核处理器而言,处理并发线程,就是在分配时间片给到每个并发线程不停地切换任务,所以所谓的并发并不是真的同时进行,只是无限接近并发而已,但是我要说明一下时间片轮转速度是非常快的,很多人疑问这个轮转速度是由什么保证的呢?----这就要考虑到硬件支持了,博主也是电子专业对其有一些了解,下次我再给你们介绍,现在画个饼哈哈哈)因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储(如图所示,程序计数器及记录着每个线程运行到哪了,这样下次时间片轮转再次轮到你,你可以接着运行),称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。

不知道通过上面的例子大家有没有理解,不理解的话我下面举个例子:

线程A在看直播,突然,线程B来了一个视频电话,就会抢夺线程A的时间片,就会打断了线程A,线程A就会接电话,然后,视频电话结束,这时线程A究竟该干什么? (线程是最小的执行单位,他不具备记忆功能,他只负责去干,那这个记忆就由:程序计数器来记录)这样视频电话结束了以后又可以回到直播继续播放

三:栈

  1. java虚拟机是线程私有的,它的生命周期和线程相同

  2. 虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息

  3. 如图所示,其中每个栈信息的属性解释

  • 局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型(returnAddress中保存的是return后要执行的字节码的指令地址)

  • 操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去

  • 动态链接:假如我方法中,有个 service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方意思就是要在方法中转化到另外一个方法中去,这样就需要动态衔接

  • 出口:出口是什呢,出口正常的话就是return 不正常的话就是抛出异常落

如何设置栈的内存大小?

使用参数-Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。 (IDEA设置方法:Run-EditConfigurations-VM options 填入指定栈的大小-Xss256k)

思考:

一个方法调用另一个方法,会创建很多栈帧吗?

答:会创建。如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面

栈指向堆是什么意思?

栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址,堆中的数据等下讲

递归的调用自己会创建很多栈帧吗?

递归的话也会创建多个栈帧,就是一直排下去,沒有結束条件就会产生oostack的风险,下面的例子就是爆栈的实际例子:

public class StackOverFlowErrorTest {public static void main(String[] args) {//递归调用mainmain(args);}
}

四:堆

  • java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例

  • 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配

  • java堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”(后面在说GC垃圾回收机制的时候你就会明白这个名词)

  • 从内存回收角度来看java堆可分为:新生代和老生代

  • 从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。

  • 无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存(其实都在堆区域,只不过为了加快回收算法就细化了一些区域,毕竟堆空间区域很大)

  • 根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中(正因为这个不连续性所以我们堆空间的位置都需要一个指针去维护它,不然很容易丢失,导致堆空间无法释放,内存泄漏)。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

堆的细分内存结构

一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速(存入新生代)

另外一类对象时生命周期非常长,在某些情况下还能与JVM的生命周期保持一致 (存入老年代)

Java堆区进一步细分可以分为新生代(YoungGen)和老年代(OldGen),新生代可以分为伊甸园区(Eden)、新生区1(from)和新生区2(to)

JDK8及以后

为什么要把Java堆分代?不分代就不能正常工作了么?

经研究,不同对象的生命周期不同。70%-99%的对象都是临时对象,新生代:有Eden、Survivor构成(s0,s1 又称为from to),to总为空,老年代:存放新生代中经历多次依然存活的对象。

其实不分代完全可以,分代的唯一理由就是优化GC性能。 如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。 GC的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描,而很多对象都是朝生夕死的。 如果分代的话,把新创建的对象放到某一地方,当GC的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

永久代为什么要被元空间替换?

由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。

这项改动是很有必要的,原因有:

(1)为永久代设置空间大小是很难确定的。

在某些场景下,如果动态加载类过多,容易产生Perm区的O0M,比如某个实际Web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。 “Exception in thread’ dubbo client x.x connector’java.lang.OutOfMemoryError: PermGenspace”而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

(2)对永久代进行调优是很困难的

这篇关于java程序在运行过程各个内部结构的作用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1053403

相关文章

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("