JVM 从入门到精通(六)JVM运行时数据区——虚拟机栈3

简介: JVM 从入门到精通(六)JVM运行时数据区——虚拟机栈3

8.4 方法重写的本质


动态语言和静态语言


1.动态类型语言和静态类型语言两者的区别就在于 对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之是动态类型语言。


2.说的再直白一点就是,静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征。

Java:String info = "mogu blog";       (Java是静态类型语言的,会先编译就进行类型检查)
JS:var name = "shkstart";    var name = 10; (运行时才进行检查)


方法重写的本质


Java 语言中方法重写的本质:


1.找到操作数栈顶的第一个元素所执行的对象的 实际类型,记作C。


2.如果 在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验


1.如果通过则返回这个方法的直接引用,查找过程结束


2.如果不通过,则返回java.1ang.IllegalAccessError 异常


3.否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。


4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。


IllegalAccessError介绍


1.程序试图访问或修改一个属性或调用一个方法,这个属性或方法,你没有权限访问。


2.一般的,这个会引起编译器异常。这个错误如果发生在运行时,就说明一个类发生了不兼容的改变。


3.比如,你把应该有的jar包放从工程中拿走了,或者Maven中存在jar包冲突


回看解析阶段


1.解析阶段就是 将常量池内的符号引用转换为直接引用的过程


2.解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等


8.5 多态与虚方法表


虚方法表


1.在面向对象的编程中,会很频繁的使用到 动态分派,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适的目标的话就可能 影响到执行效率。


2.因此,为了提高性能,JVM采用在类的方法区建立一个虚方法表(virtual method table)来实现,非虚方法不会出现在表中。使用索引表来代替查找。


3.每个类中都有一个虚方法表,表中存放着各个方法的实际入口。


4.虚方法表是什么时候被创建的呢?虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM会把该类的虚方法表也初始化完毕。


5.如图所示:如果类中重写了方法,那么调用的时候,就会直接在该类的虚方法表中查找



九、方法返回地址


方法返回地址(return address)


1.存放调用该方法的pc寄存器的值。一个方法的结束,有两种方式:


1.正常执行完成


2.出现未处理的异常,非正常退出


2.无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。


3.本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。


4.正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。


方法退出的两种方式


当一个方法开始执行后,只有两种方式可以退出这个方法


正常退出:


1.执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;


2.一个方法在正常调用完成之后,究竟需要使用哪一个返回指令,还需要根据方法返回值的实际数据类型而定。


3.在字节码指令中,返回指令包含:


1.ireturn:当返回值是boolean,byte,char,short和int类型时使用


2.lreturn:Long类型


3.freturn:Float类型


4.dreturn:Double类型


5.areturn:引用类型


6.return:返回值类型为void的方法、实例初始化方法、类和接口的初始化方法


异常退出:


1.在方法执行过程中遇到异常(Exception),并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,简称异常完成出口。


2.方法执行过程中,抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码



代码举例


代码

public class ReturnAddressTest {
    public boolean methodBoolean() {
        return false;
    }
    public byte methodByte() {
        return 0;
    }
    public short methodShort() {
        return 0;
    }
    public char methodChar() {
        return 'a';
    }
    public int methodInt() {
        return 0;
    }
    public long methodLong() {
        return 0L;
    }
    public float methodFloat() {
        return 0.0f;
    }
    public double methodDouble() {
        return 0.0;
    }
    public String methodString() {
        return null;
    }
    public Date methodDate() {
        return null;
    }
    public void methodVoid() {
    }
    static {
        int i = 10;
    }
    public void method2() {
        methodVoid();
        try {
            method1();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void method1() throws IOException {
        FileReader fis = new FileReader("atguigu.txt");
        char[] cBuffer = new char[1024];
        int len;
        while ((len = fis.read(cBuffer)) != -1) {
            String str = new String(cBuffer, 0, len);
            System.out.println(str);
        }
        fis.close();
    }
}


方法正常返回


ireturn



dreturn



areturn



异常处理表:


反编译字节码文件,可得到 Exception table


from :字节码指令起始地址


to :字节码指令结束地址


target :出现异常跳转至地址为 11 的指令执行


type :捕获异常的类型



十、相关面试题


1.举例栈溢出的情况?(StackOverflowError)


递归调用等,通过-Xss设置栈的大小;


2.调整栈的大小,就能保证不出现溢出么?


不能 如递归无限次数肯定会溢出,调整栈大小只能保证溢出的时间晚一些,极限情况会导致OOM内存溢出(Out Of Memery Error)注意是Error


3.分配的栈内存越大越好么?


不是 会挤占其他线程的空间


4.垃圾回收是否会涉及到虚拟机栈?


不会



关于Error我们再多说一点,上面的讨论不涉及Exception


首先Exception和Error都是继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。


Exception和Error体现了JAVA这门语言对于异常处理的两种方式。


Exception是java程序运行中可预料的异常情况,咱们可以获取到这种异常,并且对这种异常进行业务外的处理。


Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。


其中的Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常 必须在编写代码时,使用try catch捕获(比如:IOException异常)。非检查性异常 在代码编写使,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的。


5.方法中定义的局部变量是否线程安全?


要具体情况具体分析

/**
 * 面试题:
 * 方法中定义的局部变量是否线程安全?具体情况具体分析
 *
 * 何为线程安全?
 *     如果只有一个线程可以操作此数据,则必定是线程安全的。
 *     如果有多个线程操作此数据,则此数据是共享数据。如果不考虑同步机制的话,会存在线程安全问题
 *
 * 我们知道StringBuffer是线程安全的源码中实现synchronized,StringBuilder源码未实现synchronized,在多线程情况下是不安全的 * 二者均继承自AbstractStringBuilder * */
public class StringBuilderTest {
    //s1的声明方式是线程安全的,s1在方法method1内部消亡了
    public static void method1(){
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        s1.append("b");
    }
    //stringBuilder的操作过程:是不安全的,因为method2可以被多个线程调用
    public static void method2(StringBuilder stringBuilder){
        stringBuilder.append("a");
        stringBuilder.append("b");
    }
    //s1的操作:是线程不安全的 有返回值,可能被其他线程共享
    public static StringBuilder method3(){
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        s1.append("b");
        return s1;
    }
    //s1的操作:是线程安全的 ,StringBuilder的toString方法是创建了一个新的String,s1在内部消亡了
    public static String method4(){
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        s1.append("b");
        return s1.toString();
    }
    public static void main(String[] args) {
        StringBuilder s = new StringBuilder();
        new Thread(()->{
            s.append("a");
            s.append("b");
        }).start();
        method2(s);
    }
}


目录
相关文章
|
2月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
2月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
3月前
|
Arthas 监控 算法
JVM成神路终章:深入死磕Java虚拟机序列总纲
JVM成神路终章:深入死磕Java虚拟机序列总纲
|
2月前
|
监控 算法 Java
深入理解Java虚拟机:JVM调优与性能提升
本文旨在为Java开发者提供一条清晰的路径,以深入掌握Java虚拟机(JVM)的内部机制和性能调优技巧。通过具体案例分析,我们将探讨如何识别性能瓶颈、选择合适的工具进行监控与调试,以及实施有效的优化策略,最终达到提高应用程序性能的目的。文章不仅关注理论,更注重实践应用,帮助读者在面对复杂的Java应用时能够游刃有余。
57 0
|
3月前
|
存储 Java 对象存储
Java虚拟机(JVM)中的栈(Stack)和堆(Heap)
在Java虚拟机(JVM)中,栈(Stack)和堆(Heap)是存储数据的两个关键区域。它们在内存管理中扮演着非常重要的角色,但各自的用途和特点有所不同。
42 0
|
3月前
|
存储 算法 Java
(四)JVM成神路之深入理解虚拟机运行时数据区与内存溢出、内存泄露剖析
前面的文章中重点是对于JVM的子系统进行分析,在之前已经详细的阐述了虚拟机的类加载子系统以及执行引擎子系统,而本篇则准备对于JVM运行时的内存区域以及JVM运行时的内存溢出与内存泄露问题进行全面剖析。
|
6天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
18 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
2月前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
2月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
2月前
|
存储 Java 程序员
JVM自动内存管理之运行时内存区
这篇文章详细解释了JVM运行时数据区的各个组成部分及其作用,有助于理解Java程序运行时的内存布局和管理机制。
JVM自动内存管理之运行时内存区