Java虚拟机

JVM整体架构dd

JVM整体架构
  首先会编译为class文件,在使用java命令。从而根据不同OS编译成不同的二进制编码运行。
  比如根据不同的OS有不同的JDK版本。不同的JDK版本就有不同JVM的底层实现,所以真正的运行实现靠的是JVM。

在这里插入图片描述
主要包括三部分:
   * 类装载子系统:将字节码文件装载在对应的内存区里,如堆、栈。
   * 运行时数据区
   * 执行引擎:执行加载的类

后续主要针对这段代码讲解解!虚拟机之间的联系。

package com.example.lib;

public class MathW {
    public static int initData = 666;
    public static User user = new User();

    public int compute(){
        int a = 1;
        int b = 2;
        int c = (a+b)*10;
        return c;
    }

    public static void main(String[] args) {
        MathW math = new MathW();
        math.compute();
        System.out.println("main down");
    }
}

那虚拟机之间是怎么协作的呢???

先看下面整图,后面慢慢的详细介绍它们之间的工作流程

在这里插入图片描述

   运行时存放局部变量。每一个线程在开始运行过程中,都会给这个线程分配一块自己的栈内存控件(如main线程,会给main一块栈内存)。
   由于每一个线程内部都会包含多种方法调用(如上图左边是main线程的方法调用,包括main方法和compute方法),所以需将该线程分配的这块栈内存又分为多个细小的栈帧,为每一个方法调用分配一块栈帧,方法内所用到的局部变量会被存储在这个栈帧里。如果该调用结束,则对应的栈帧被销毁。同理,如果线程结束,栈内存会被收回。

栈帧内部的结构更为复杂!!! && 栈与JVM结构的联系

   众所周知,JVM是先用javac将其变为class字节码文件的,那么如何反编译呢?----- javap
   使用 javap -c xxx.class命令即可反编译
在这里插入图片描述

这些JVM指令都可以在网上找到。JVM指令
在这里插入图片描述
   上述指令片段对应的是这段代码。
   a = 1:先将1存入操作数栈中,然后在局部变量表创建一个a变量,并将操作数栈中的1出栈赋值给局部变量表中的a。
   b = 2:同理a = 1。
   c = (a+b)*10:首先将局部变量表中a和b的值拿出来放入操作数栈中;其次弹出做加法并压入新值到操作数栈中;然后将10压入操作数栈;随后弹出两个元素做乘法,将新值30压入操作数栈中;最后在局部变量表创建c变量,并将操作数栈中的30出栈赋值给局部变量表中的c。
   return c:将30存到操作数栈中,然后返回到main()-栈帧中。

So操作数栈可以认为是程序执行过程中操作数临时存放的内存区域!!!

   程序计数器也是内存区域,是每个线程所独享的,存放的是该线程当前运行的代码的行号------字节码执行引擎动态修改程序计数器(真正的代码执行也是执行引擎去做的)。

此时compute()方法调用完毕,接着应该返回main中的System.out.println("main down")了,但虚拟机怎么知道返回这行代码呢????

   这就用到了方法出口,果然虚拟机每一块内存分配都是有道理的,学到了学到了!!! main()调用compute()方法时,会将main()本身栈帧中的程序计数器的值传给compute()栈帧中的方法出口中,然后返回main()时就可以知道啦。

   我们都知道对象是存到里面去的,也就是main中math对象存在了中,可是main的栈帧中也维护了一个局部变量表,该局部变量表也存放了一个math,那这两个math之间有联系吗?
   当然有啦! 局部变量表中的math是一个地址引用,指向的就是中的math。

方法区

   整个字节码文件真正被java虚拟机的类转载子系统装载到了方法区,jdk1.8之后方法区改名叫元空间了,存放的是常量+静态变量+类元信息(类的名称、方法名、修饰符、返回值等)。如第一张图中的user对象,存在中,但是由于它是static的,所以在方法区也会有一个user,存放的是地址引用,指向中的user对象。每个中也有对象也存在一个指针,指向原始类和类元信息。所以同理,堆也会指向方法区
   同时,MathW.java中的compute方法的类元信息也会存到方法区中,

那么程序执行过程中,是如何找到compute方法并执行的呢???

   这就用到了动态链接,先看定义:将符号引用(源代码每一行都可以当做符号)在程序运行过程中转变直接引语。
   在运行到math.compute()这一行,它是一个静态的符号,它的类元信息是被装载到方法区里面的,所以在真正运行的时候,就是通过math对象的对象头所存储的类元信息找到方法运行的指令码的地址,然后将这个地址存到动态链接里面。

本地方法栈

   native方法 — 寻的不是java实现,而是c语言实现。用作java和c的跨语言交互。

   每一个线程都会独享一个本地方法栈,存放的是本地方法的局部变量

垃圾回收

垃圾回收前得说一下堆

   堆内部有Eden、年轻代、老年代,老年代占堆内存的三分之二,年轻代是三分之一。new出来的对象放在年轻代里面。

年轻代满了怎么办???

   Minor GC—回收年轻代。是通过执行引擎去调用这个Minor GC的

那它是如何回收的呢?

   在一开始的代码中,系统会生成一个main线程,这个线程内部有两个栈帧,在main()方法结束后这个栈也就被销毁了,则new出来的math对象也被销毁的,但注意这里销毁的只是对math对象的堆引用,堆内部这个math还是存在的!!!但没有任何指针引用,全都是垃圾、无引用对象。

GC Roots根节点

根节点:线程栈的本地变量、静态变量、本地方法栈的变量
   main()方法没销毁前,GC Roots根都可以找到堆中的引用,都是可达的,但如果方法结束了,这个引用也就没有了,GC Roots根就无法找到堆中对应的对象,所以这个堆对象就被标记可销毁了!
   Minor GC后存活的对象会被放到From区,每次GC后存活的分代年龄+1,后被放到To区,如果又一轮GC后存活,会从To中拿出放到From区。这样来来回回差不多经历15次后,若还没有被销魂,才会被移到老年代。如果老年代溢出了会触发OOM并执行Full GC,同时STW(停止正常应用线程的执行)

Full GC会在整个堆空间回收,会清理所有老年代对象

Java虚拟机调优思路

   Full GC虽好,可不要贪多哦。因为它会导致系统卡顿,这就免不了调优。所以调优的思路就是减少Full GC次数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值