• JVM(1)——简介


    网上流传着一段挺有意思的话……

    对于从事C或C++的开发人员来说,他们既是内存管理的最高权力的皇帝,也是最基础的劳动人民,担负着每一个对象生命开始到终结的维护工作,有点光杆司令的赶脚。

    但对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不需要手动维护,也不容易出现内存泄漏和溢出的问题。但是如果不了解JVM,一旦出现问题,你就会变得无从下手……

    一、JVM

      Java Virtual Machine(java虚拟机),一种虚构的计算机,通过在实际计算机上模拟各种计算机功能实现的。
    

    JVM结构基本上由三大部分组成:类加载器(ClassLoader subsystem),执行引擎(Execution Engine),内存区(Runtime data areas)。
    这里写图片描述

    1、类加载器

    在JVM启动或类运行时将需要的class字节码(下图的*.class文件)加载到JVM中。
    初始类加载器有三个层次:根类、扩展类和系统类加载器。

    1)Bootstrap ClassLoader(根类)
    2)Extension ClassLoader
    3)System ClassLoader

    2、执行引擎
    负责执行class文件中的指令,得到执行结果。
    本质是执行一个个方法串起来的流程,对应操作系统中的一个java线程。

    3、内存区
    在执行引擎执行前、执行中,都需要存储一些东西,这就用到了java的内存管理,他将内存划分为若干区来模拟实际机器上的存储、记录和调度功能模块。

    具体的内存管理内容下面再介绍。

    二、工作原理

    java语言的跨平台性离不开JVM的作用,下图是编译执行的简化过程。JVM屏蔽了各个计算机平台相关的软件或硬件之间的差异,使得java不需要再考虑平台不同的问题,直接交给JVM就可以了。

    这里写图片描述

    三、内存区域

    在java虚拟机规范中将java运行时数据划分为以下几种:
    这里写图片描述
    1)线程共享

    即上图左侧蓝色内容,这两块内容是线程共享的,而不是线程私有的。

    堆是储存对象实例的地方,几乎所有的对象实例都是在这里分配的,是JVM管理对象的核心存储区域。

    因为在栈中存储的变量等数据都是随线程或方法结束而消失的,但堆中并不是这样,再加上对象实例一般占用内存也比较大,因此Java堆是垃圾收集器管理的主要区域,有时也被成为“GC堆”Garbage Collected Heap。

    在实际回收的过程中,如果每次回收都将堆中的对象实例“检查”一遍,看是不是需要回收的话,这样效率太慢了,执行起来太费事了。现在流行的分代收集算法就解决了这一问题,大概意思就是划分出不同的“年代”区域,不同的区域内回收的频率不同,如果多次检查一个实例都不需要释放回收,那我们就把他挪到下一个年代中去。

    java堆还可以细分为:新生代和老年代。新生代还可以细分为:Eden、From Survival,To Survival等。详情见下图。

    这里写图片描述

    方法区

    方法区用于存储类结构信息,包括类信息、常量、静态变量等。在HotSpot虚拟机中,方法区中为“永久代”。因此,垃圾回收器很少光顾这部分区域,这部分比老年代的回收频率更低。

    方法区中比较常用的是运行时常量池( Runtime Constant Pool),代表运行时每个class文件中的常量表,位于方法区中。

    2)其他

    内存区域图中的右侧内容,该部分是线程私有的。

    程序计数器

    指示当前线程所执行的字节码的行号,但如果执行的native方法,计数器值为空。
    很多Native方法都是用C语言实现的,通常叫他C栈,直接放入本地方法栈中执行。

    Java虚拟机栈

    java栈是线程私有的,每启动一个新线程,java虚拟机就会给他分配一个(以帧为单位的)java栈。他的生命周期和线程相同。执行的操作有两种:压栈和出栈。

    四、实例分析

    补充了这么多理论知识,是时候该来个例子巩固一下了……

    public class Person {
    
        private Integer id;
        private String name;
        private String password;
    
        public Person(Integer id, String name, String password) {
            super();
            this.id = id;
            this.name = name;
            this.password = password;
        }
    
        //get和set方法省去
    
    }
    
    //测试类
    public class Test {
        public static void main(String[] args) {
                Test test=new Test();
                int id=1;
                Person p1=new Person(2,"Sherry","123");
                Person p2=new Person(3,"yang","456");
    
                test.change(id);
        }
    
        public void change(int i){
            i=8;
        }
    }

    Step1:
    main()方法是程序的入口,当执行完main方法的前两行代码之后,内存中的情况大致如下图所示:

    注意:
    图中的“xxx”代表持有引用的地址。
    虽然在上面的内存图中,左侧为堆右侧为栈,但为了更清楚的表示引用关系,这里就把栈画在左侧,堆在右侧。

    这里写图片描述

    Step2:

    执行完第四行代码时

    注意:此时test和id的内存块并不会释放,因为对象还未死,或者说引用还在。使用new创建的对象实例保存在堆中,但变量p1、p2在栈中,持有着实例的地址,即图中的“xxx”。

    这里写图片描述

    Step3:

    第五行代码调用change(),此时需要创建一个新的栈帧。当调用结束之后,变量i的内存块和change()方法的栈帧一并消失。

    这里写图片描述

    小结

    这篇博客只是对jvm进行了简单的介绍,很多东西只是提了一下,没有深入了解,后续会进行补充和完善。如果哪里理解的不合理,还请交流指正。

  • 相关阅读:
    winphone 开发学习笔记(1)
    Performance testing of web application
    每天几步一点点
    其实做测试也不是想象中的简单
    【2019杭州集训12.09】向量
    【2019杭州集训】12.09训练总结
    【2019杭州集训】12.08训练总结
    burnside定理学习小计
    【CSP-S2019D2T3】树的重心
    【CSP-S2019D1T3】树上的数
  • 原文地址:https://www.cnblogs.com/saixing/p/6730216.html
Copyright © 2020-2023  润新知