深入理解JVM

4 篇文章 0 订阅

定义

JVM则是JRE中的核心组成部分,承担分析和执行Java字节码的工作

JVM通过执行引擎将通过类加载器加载.clsss文件的数据放在运行时数据区进行解释执行.

JVM运行时数据区

运行时数据区可划分为二个基本区: 线程私有和线程共享

线程私有: 程序计数器,虚拟机栈,本地方法栈

        程序计数器: 它主要是记录线程运行到哪里,比如一个线程里代码运行到一半,忽然CPU执行权被其它线程抢走了,这时就是程序计数器就要记录下我这线程运行到这,然后下次抢到执行权又从记录的这一块开始

        虚拟机栈: 虚拟机栈是由一个一个的栈桢组成,一个方法就是一个栈桢,它的模式是先进后出,先进的方法,是最后出栈的

                栈桢: 由局部变量表,操作数栈,动态连接,完成出口组成

                        局部变量存储的是8大基本类型和类的引用 当一个方法里执行了int a = 1;时,这个时候1就会入操作数栈,然后再将a=1储存到局部变量表中

                        操作数栈是解释执行虚拟机数据的

                        动态连接这个一般是处理多态,比如一个人,它可以是男人也可以是女人,当开始创建一个男人,男人要上厕所,执行WC方法,过不多久又又上面男人的引用创建了一女人,女人这里也要上厕所,执行WC方法,动态连接这个就是用来是执行男人上厕所方法还是女人上厕所方法

                        完成出口方法要出栈肯定有个结束,有返回值这个时候也要将返回值储存到局部变量表中        

        本地方法栈: 主要是存放native方法的

线程共享: 方法区,堆        

        方法区:一片连续的堆空间主要存放的是静态变量,常量,即时编译后的代码

                即时编译后的代码就是匿名内部类,动态代码生成的内存类

        共享区域为什么要用二个区域来表示呢? 因为方法区里的内存基本是不变的, 而堆里面的内存会出现频繁回收,所以为了效率就分为了二个

        而我们Androdi中的内存优化主要涉及的就是堆这一块内存

        堆内存又分为几大区域,新生代,老年代 元空间(永久代)

                新生代: 一般情况下新来的对象都是先来新生代报道的,新生代又分为三个区域,eden from to三个空间,为为什么要三个,主要涉及到了垃圾回收算法

                老年代: 一个对象经过垃圾回收15次还没有回收掉的时候,这处存活很高的对象就会转移到老年代

                元空间: 存放的是方法区里的内存,但是也会出现OOM,因为即时编译后的代码过大就会

Android应用程序是运行在Dalvik/ART虚拟机上,并且每一个应用程序都有自己独立的虚拟机.

Dalvik虚拟机也算是一个Java虚拟机,只不过android执行的是dex文件 ,而Java执行的是.class文件

Android虚拟机和Java虚拟机特性其实是差不多了,它也是基于Java虚拟机开发出来的,差别就在于二者执行的指令集不一样,andorid是基于寄存器,而java是基于操作数栈的
 

public class Person {

    public int work(){
        int x= 1;
        int y = 2;
        int z = (x+y)*10;
        return z;
    }

    public static void main (String [] args){
        Person person = new Person();
        person.work();
    }
}

上面这一段代码是一个java文件 ,它要运行的话会通过javac把Person.java文件编译成Person.class文件

通过

javap -c Person.class命令会将这个class文件编译成字节码汇编码,我们的代码是会执行的,也就是说它会像指针一样走到哪里就指到哪里

public class com.vanke.lib.Person {
  public com.vanke.lib.Person();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int work();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/vanke/lib/Person
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method work:()I
      12: pop
      13: return
}

Code:这个行号,它是针对方法体的偏移量,大体上可以解释为是程序计数器记录字节码的地址

虚拟机栈(好比一个子弹夹)  一个个子弹为一个个栈帧(一个栈帧就是一个方法) 

栈帧:

栈帧:包括:局部变量表 操作数栈, 动态连接,完成出口  大小是受限制的可以通过-Xss来修改

局部变量表:存储局部变量的,比如work()方法里的int x;  int y; 只能存储8大基本类型加一个引用. 引用就是上面说的Person person = new Person();这行代码里红色标记的东西

操作数栈: 方法的操作(比如打枪的方法,用手打,用脚打,各种方式)

就比如上面的例子

一个线程有一个虚拟机栈,当执行到main()方法里,main()方法就是一个栈帧,这个时候main()方法入栈

在main()方法里走着走着,它会调用work()方法,这个时候main()它会被压下去,然后work()方法入栈

现在我们主要来分析一下work()方法,上面我们通过一行命令将.class文件翻译成了机器码如下


public class com.vanke.lib.Person {
  public com.vanke.lib.Person();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int work();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
}

当执行到iconst_1       将int值为1 入操作数栈

当执行到istore_1时    将操作数栈栈顶的int型数值存入到局部变量表,存在局部变量表下标为1的位置,在局部变量表中的第0位置存的基本是this,如果是静态方法则不是this.因为非静态方法要通过this才能调用其它方法

当执行到iconst_2       将int值为2 入操作数栈

当执行到istore_2时    将操作数栈栈顶的int型数值存入到局部变量表,存在局部变量表下对应位置

iload_1 将x拿出来压入操作数栈

iload_2 将y拿出来压入操作数栈

iadd  先将2出栈 再将1出栈再执行相加得到3  再把3压入操作数栈

bipush   10    将10的值拓展为int压入操作数栈

imul 10出栈  3出栈 然后相乘 得到30压入操作数栈

istore_3  将操作数栈的30存入局部变量表

iload_3 因为要返回所以要将30这个值压入操作数栈

ireturn 返回

java 解释执行基于操作数栈   c是基于寄存器

优缺点: 寄存器快,寄存器是基于硬件,缺点移植性差.  操作数栈兼容性好,速度偏低一点点

动态连接 (java里有多态,表态分派,动态分派)
 

Women extends Person

Man extends Preson

Person ancely = new Women();

ancely.work();

ancely = new Man();

ancely.work();

如上面一段代码 work()方法JVM不知道执行谁的work方法,所以就有一个动态连接来确定

完成出口: (返回地址,正常会调用程序计算器中的地址进行返回)

本地方法栈

本是方法栈是保存navite方法的信息

当jvm创建的线程调用native方法后,jvm不再为其在虚拟机栈中创建栈桢,jvm只是简单的动态链接并直接调用native方法, 程序计数器是不会记录的

HotSpot直接把虚拟机栈和本地方法栈合并

方法区(<=JDK 1.7叫永久代  >=1.8叫元空间)

存放类信息 常量 静态变量,即时编译后的代码

永久代:受制于堆内存大小

元空间(tenured):它可以使用机器内存,默认不受限制,方便拓展,但是它会挤压堆空间

Oracle 收构了二家公司 Hotspot JRocket  Hotspot有元空间和永久代 JRocket没有永久代

堆(-Xmx 堆区可分配的最大上限   -Xms  堆区内初始分配的大小)

存放对象实例和数组

共享区为什么要用二个区来呢?

原因: 因为堆存放的是对数和数组,这些东西都是会频繁回收(GC),而类信息 常量 静态变量非常难回收的.所以就有动态分离,便于回收;

直接内存(堆外内存)

如果使用NIO的话这块区域会频繁使用,在java堆内可以直接引用directByteBuffer对象直接引用并操作,这块内存不会java堆大小限制,可以通过MaxDirectMemortSize来设置,默认大小跟堆内存一样大  

下面详细分析一下JVM运行时内存的分布

先写一段java代码



/*
 *  @项目名:  My Application
 *  @文件名:   JVMObject
 *  @创建者:   fanlelong
 *  @创建时间:  2021/5/12 10:29 PM
 *  @描述:    -Xmx30m  -Xms30m  -XX:+UseConcMarkSweepGC  -XX:-UseCompressedOops  -Xss1m
 *
 */

public class JVMObject {
    public static final String MAN_TYPE = "man";//常量
    public static String WOMEN_TYPE  = "women";//静态变量
    public static void main (String [] args) throws InterruptedException {//栈桢
        Teacher T1 = new Teacher();//堆中  T1是局部变量
        T1.setName("Anceyl");
        T1.setSexType(MAN_TYPE);
        T1.setAge(28);
        for (int i = 0; i < 15; i++) {//进行15次垃圾回收
            System.gc();
        }

        Teacher T2 = new Teacher();
        T2.setName("Lelong");
        T2.setSexType(MAN_TYPE);
        T2.setAge(18);
        Thread.currentThread().sleep(Integer.MAX_VALUE);
    }
    public static class Teacher {
        String name;
        String sexType;
        int age;


        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
        public void setAge(int age) {
            this.age = age;
        }

        public String getSexType() {
            return sexType;
        }

        public void setSexType(String sexType) {
            this.sexType = sexType;
        }
    }
}

1: 程序启动时,要向操作系统申请内存(栈是多大,堆是多大,方法区多大)

2: 类加载:通过类加载器,会把JVMObject.class  Teacher.class放入我们方法区

3: 方法区解析方法时,发现有常量和静态亦是,这时就把 MAN_TYPE 和 WOMEN_TYPE放入到方法区

4:运行代码了,main()方法运行进来,这是虚拟机栈创建起来并运行main(),这时就会压入一个main()方法的栈桢
5: 栈桢中的方法执行了: 执行到 Teacher T1 = new Teacher();时,会在堆中分配一块内存,然后把把T1这个引用压到main()方法栈桢的局部变量表中

6:中间执行了一些方法,这些方法就不断的压入虚拟机栈

7: 执行循环15次 ..................T1这块内存进入到老年代

8 Teacher T2 = new Teacher(); 堆中eden中分配内存,T2这个引用压到main()方法栈桢的局部变量表中

内存可视化工具HSDB(java -cp.\sa-jdi.jar sum.jvm.hotspot.HSDB)

java -cp "C:\Program Files\Android\Android Studio\jre\lib\sa-jdi.jar" sun.jvm.hotspot.HSDB

jps 查看进程  ps -ef|grep java-->查看linuxjava进程

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值