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);
    }
}


目录
打赏
0
0
0
0
90
分享
相关文章
|
7月前
|
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
179 27
|
2月前
|
JVM深入原理(七)(一):运行时数据区
栈的介绍:Java虚拟机栈采用栈的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存栈的组成:栈:一个线程运行所需要的内存空间,一个栈由多个栈帧组成栈帧:一个方法运行所需要的内存空间活动栈帧:一个线程中只能有一个活动栈帧栈的生命周期:栈随着线程的创建而创建,而回收会在线程销毁时进行栈的执行流程:栈帧压入栈内执行方法执行完毕释放内存若方法间存在调用,那么会压入被调用方法入栈,执行完后释放内存,再执行当前方法,直到执行完毕,释放所有内存。
33 0
|
2月前
|
JVM深入原理(七)(二):运行时数据区
堆的作用:存放对象的内存空间,它是空间最大的一块内存区域.栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。堆的特点:线程共享:堆中的对象都需要考虑线程安全的问题垃圾回收:堆有垃圾回收机制,不再引用的对象就会被回收方法区的概述:方法区是存放基础信息的位置,线程共享,主要包括:类的元信息:保存了所有类的基本信息运行时常量池:保存了字节码文件中的常量池内容静态常量池:字节码文件通过编号查表的方式找到常量。
38 0
JVM 运行时数据区
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这 些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区 域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解 析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳 转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; 为什么要线程计数器?因为线程是不具备记忆功能 Java 虚拟机
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
7月前
|
JVM运行时数据区
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一
94 2
|
2月前
|
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
249 55
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
141 6
登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问