一、jvm详细讲解,jvm运行原理

准备向架构师方向发展的小伙伴我们可以一起学习,接下来我会一边学习一边发文章,如果我写的有什么问题,或者你有什么问题,欢迎留言讨论

jdk的体系结构

在这里插入图片描述

jvm(java虚拟机)的用途

在这里插入图片描述
HelloWorld我们都写过,他是怎么在我们系统上运行的呢,那就是我们jvm的作用了。看图说话:Helloword.java通过jdk的javac命令将.java文件编译成.class文件。然后,就可以在jvm上运行了。
java具有跨平台性,那么跨平台性事怎么实现的呢?我们不同平台cpu运行的是自己的机器码,机器码是不一样的。那就是jvm的作用了,jvm是在jdk中的,不同版本的jdk的jvm也是不同的,jvm是从软件层面屏蔽不同操作系统在底层偏硬件与指令上的区别。帮我们生成 基于不用操作系统特定的二进制机器码,然后在不同平台上去运行。

jvm的虚拟机的组成

jvm分为3部分,类装载子系统,java的内存区域,字节码执行引擎
jvm 运行过程是.class文件首先到类装载子系统,然后加载到内存区域,最后是字节码执行引擎去运行我们内存中的代码,这就是我们jvm完整的组成部分。
在这里插入图片描述

  1. 栈(线程)
    栈内部是有很多很多栈帧。当我们运行main方法的时候,会启动一个线程,线程启动的过程中,会开辟一片空间,在这片空间中会开辟一片栈内存的空间。当然整个线程的栈空间不可能全部给这个方法用,在main方法启动的时候jvm会给这个方法专属的内存空间。main方法运行后,会启动test的方法还会开辟另一个专属的内存空间,存放该方法的局部变量。每个方法对应的内存空间我们叫做栈帧内存空间。这个栈和我们数据结构的栈是一样的,也是先进后出。
    为什么这么先进后出呢,看下边代码main先运行,我们存到栈中,然后运行test,我们接着存到栈中,然后test方法先结束,test方法出来,然后是main方法运行结束,main方法出来。所以要先进后出
    在这里插入图片描述
    栈中存放的不只是局部变量表,还有操作数栈,动态链接和方法出口
  • 局部变量表中存的是局部变量,别的就不讲了,这里主要讲一下对象,如果我new一个对象a,局部变量表中也会存a,大家都知道对象应该存到堆中的,那么a为什么会存到栈的局部变量表中,这个局部变量表中的a只是存了对象的位置即调用指针,指向堆的位置,对象还是在堆中的。这也是堆和栈的区别。

  • 操作数栈是什么?
    逻辑我们从java代码中去找是找不到的,我们要看一下源码,这就要用到javap -c的命令了,打开控制台输入命令,javap -c TestController.class > test.txt
    在这里插入图片描述
    在这里插入图片描述
    这些源码看不懂我们可以到oracle官网去查 https://www.cnblogs.com/lsy131479/p/11201241.html 这个网址是我刚找到的翻译过来的。
    接下来我们看源码说一下操作数栈,main方法的第一行开始 iconst_1 代表将int类型常量1压入栈,就是将常量1存放到了操作数栈,istore_1 代表将int类型值存入局部变量1 ,就是将常量1弹出操作数栈存放到常量的位置,即存局部变量的区域是a=1 ,操作数栈现在是空的了。我们生成源码后可以通过上边的手册来查找。找到对应的就可以了。所以操作数栈就是用于临时数据处理的一个临时空间

  • 动态链接
    存的就是方法在方法区中内存位置,根据动态链接可以通过方法区找到方法。

  • 方法出口
    存放的是调用这个方法的位置,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

  1. 程序计数器
    每个线程启动后不仅创建了栈内存,还会创建程序计数器空间,都是从线程创建的内存空间程序计数器存的是代码的运行位置。为什么要设计程序计数器,举个栗子,项目中启动了一个线程,正在运行,这时候又有一个优先级高的线程运行,我们前一个线程就需要挂起,当后一个线程结束后挂起的线程会继续运行,那么挂起的线程应该从什么位置运行?从头运行吗?当然不是的要从之前挂起的时候的位置继续运行,这样程序计数器就起到了它的作用,这个线程需根据程序计数器中存的位置继续运行。每一个线程都有自己的程序计数器。程序计数器的值是谁修改的?就是我们jvm第三个区域字节码执行引擎修改的。

  2. 本地方法栈
    存放本地方法的内存空间,如果线程中调用过本地方法,那么就会创建本地方法内存空间。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

  3. 方法区
    它与堆一样,是被线程共享的区域
    方法区中都存了什么?存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。如果常量中有对象,对象的位置还是在堆中,方法区和堆的关系和堆和栈是一样的。


  4. 堆分为两个代,年轻代和老年代。年轻代分为两个区,Eden区和Survivor区。
    对象一般会放到Eden区,当Eden区放满了之后,会触发jvm的垃圾回收机制(minor gc),这个垃圾回收机制是谁调用的呢,是字节码执行引擎调用的。
    在这里插入图片描述

minor gc 垃圾回收机制

运用可达性分析算法。
可达性算法的起点是gc root,也就是需要通过GC Root 找出所有活的对象,那么剩下所有的没有标记的对象就是需要回收的对象。

通过查询有用的对象,做标记移到Survivor区,将没有用的对象从Eden清除。接着循环,现在Eden是空的,当Eden又满了的时候,jvm不仅会回收Eden区,还会继续回收Survivor区,和Eden区回收机制一样,只不过存放的的Survivor区的另一块内存S1,接着循环Survivor区的活着的对象会存放到另一片空着的区域,如图就是S0区域,这样无限循环,每次循环会给这个对象一个分代年龄。Survivor区的循环就是两块区域来回切换,每次这个对象的分代年龄都会+1,当分代年龄达到15的时候(大多数是15,CMS是6),这个对象会被移到老年代。这就是大概的垃圾处理过程。

那我们想一下我们的项目中什么会被存到老年代,数据库连接池,静态变量,线程池等最终都会被存到老年代。老年代存着什么东西?就是一直存活者,生命力比较顽强的老顽固对象。

分代年龄存在哪里?对象头,对象不仅仅是单存的对象,它还有对象头,存了锁,分代年龄等东西。这里就不细讲了,下边的图是具体的内容
在这里插入图片描述

gc root 在哪里?所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。说白了方法区中,栈中都有gc root 。

当老年代满了之后怎么办呢,jvm会执行full gc ,当然也是由字节码执行引擎来运行的。full gc 会对整个堆进行垃圾处理。当我们写一个死循环,full gc 也无法处理的时候,老年代依然是满的,那么接下来会出现什么问题呢?内存溢出

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值