JVM初步

写在前面:在漫长的Java框架学习完成和实习工作稳定下来之后,校招准备过程在笔者发现自己在Java基础知识方面并不扎实,虽说金九银十,但貌似越来越多的提前批招聘正紧锣密鼓的进行。感受到紧张的笔者直到现在开始进行新一轮的复习,以期夯实Java基础,盼望着在校招中找到一份好的工作。(注:此随笔作为学习笔记;笔法稀碎,再加上此前毫无写博客/随笔等习惯可能造成行文不流畅,并且有内容总结自网络,难免会有不同程度的雷同,见谅)

Java如何做到“一次编写,到处运行”?(Write once, compile anywhere)


JVM:其是Java的核心和基础,在Java编译器和操作系统之间的虚拟处理机。它是一种基于下层的操作系统和硬件平台并利用软件方法来实现的抽象的计算机,可以在上面执行Java的字节码程序
Java编译器只需面向JVM生成其所能理解的代码或字节码文件(.class)。Java源文件(.java)经过编译器编译成字节码程序,通过JVM将每一条指令翻译成不同平台的机器码,通过特定平台运行,实现“一次编写,到处运行”。
Java虚拟机屏蔽了与具体操作系统平台相关的信息细节,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

Java内存区域与内存溢出


内存区域

Java虚拟机在执行程序时会把它所管理的内存划分为若干不同的数据区域;分别可分为以下几个运行时数据区:

  • 线程私有内存区:随着线程而产生和消亡,因此不需要过多考虑内存回收的问题,并且编译时需要确定内存大小


    程序计数器:记录正在执行的虚拟机字节码指令的地址

    Java虚拟机栈:描述的是Java方法执行的内存模型(每个方法被执行的时候都会同时创建一个栈帧),为虚拟机Java执行方法服务

    1. 局部变量表
    2. 操作数栈
    3. 动态连接
    4. 方法返回地址

    本地方法栈:与虚拟机栈类似,但服务对象是本地操作系统方法

  • 线程共享内存区


    Java堆:

    Java Heap是JVM所管理内存中最大的一块,由所有内存共享;几乎所有的对象实例和数组都在此分配内存,因此它是垃圾收集器管理的主要区域,也称为“GC堆”。

    根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间而只要逻辑上连续即可。

    当堆中没有内存可分配时,并且堆无法拓展时将会抛出OutOfMemoryError异常。

    方法区:

    用于存储被加载的类信息、常量、静态变量、即时编译器编译后的代码等。

    Java虚拟机规范把方法区描述为Java堆的逻辑部分,并且允许不实现垃圾回收。

    而当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

Java类初始化


类初始化

是加载过程的最后一个阶段,到初始化阶段才真正开始执行类中的Java程序。

虚拟机规范严格规定了有且只有四种情况(称为对类的“主动引用”)必须立即对类进行初始化:

    1. 遇到new, getstatic, putstatic和invokestatic这四条字节码指令:
      • new关键字实例化对象
      • getstatic, putstatic读取或设置一个类的静态字段时(static+final修饰的字段在编译器已将结果放入常量池的静态字段除外)
      • invokestatic调用一个类中的静态方法
        (然而除了new之外,目前仍未接触过getstatic、setstatic以及invokestatic这三个字节码指令)
    2. 使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行初始化则需要先触发初始化
    3. 当初始化一个类时,若发现父类还没有初始化,需要先对父类进行初始化
    4. 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会执行该主类的初始化

而对于第3点的父类与子类的初始化问题需要注意主动引用的概念:通过子类引用父类的静态字段;
为了说明主动二字于是笔者参考其他帖子并加上自己的思考,仿造出下面的例子,借用静态代码块随着类的加载说明了关于主动和被动的两种情况

public class LoadingOfStaticBlock {
    public static void main(String[] args) {
        // 通过任意代码直接访问类中的静态变量,而不直接new子类
        // 若在此处直接new子类,将会造成对子类的加载,一并的父类会进行加载,但由于本例子重点在于主动引用,所以在这里这里不做演示
        int numberOfSon=son.number;    
    }
}

//father类
class father{
    static int number=10;
    static {
        System.out.println("父类中所包含数字为"+number);
    }
}

// son类,继承自father类
// 当father中的number变量被子类重载时,main中访问子类的static变量将变为主动引用
// (对于笔者而言,更愿意将其称为直接引用和间接引用,虽不严谨,但面对所举的例子还算是比较贴切、形象)
class son extends father{
    // static int number=100;
    static {
        System.out.println("子类中所包含数字为"+number);
    }
}

以上例子结果如下:

  • son类中不对number进行重载,此时直接访问/引用的是father类的静态成员变量而非son类中的静态成员变量,亦即被动引用了son类;因此仅对father类进行加载和初始化,而son类则不然;
  • son类中对number进行重载,此时直接访问/引用了son类所独有的静态成员变量,视为主动引用了son类;而在初始化son类后发现father类尚未初始化,于是父类亦进行初始化;

2022年8月9日随笔,借此随笔对JVM的相关知识进行复习,为第一篇随笔,写得不好或不对的地方还请多指教。

posted @ 2022-08-09 22:25  喝芬达开高达  阅读(43)  评论(0)    收藏  举报