字节码分析finally块对return返回值的影响

直接进入主题。看如下代码:

public int test(){
    int i=0;
    try {
        i=1;
        return i;
    } catch (Exception e) {
        i=2;
        return i;
    }finally{
        i=3;
    }
}

相信有点经验的程序员一眼就能说出返回的结果为1,但是您真的知道返回的结果为什么为1吗?下面我们通过分析下当前方法的字节码,来说明为什么。

查看字节码命令:javap -verbose class文件

?

知识点简单概要:
看如下字节码需要简单了解下栈的结构。
栈包括:局部变量表、操作数栈、动态连接、方法出口等。
下面字节码主要是对操作栈和局变量表的操作。

test方法的字节码如下:

 stack=1, locals=5, args_size=1
    0: iconst_0                 将常量0压入到操作栈顶
    1: istore_1                  将栈顶元素0存储到局部变量表中的第二个slot中  (slot2=0)  i=0
    2: iconst_1                 将常量1压入到操作栈顶
    3: istore_1                  将栈顶元素1存储到局部变量表中的第二个slot中  (slot2=1)  i=1
    4: iload_1                   将局部变量中第二个变量 (i=1) 压入到操作栈顶
    5: istore        4            将栈顶元素1存储到局部变量表中的第四个slot中  (slot4=1)
    7: iconst_3                 将常量3压入到操作栈顶
    8: istore_1                  将栈顶元素3存储到局部变量表中的第二个slot中  (slot2=1)  i=3
    9: iload         4            将局部变量中第四个变量 (slot4=1) 压入到操作栈顶
   11: ireturn                   返回操作栈顶值,这时操作栈中栈顶值为1。
   12: astore_2               将栈顶元素(e)存储到局部变量表的第三个slot中
   13: iconst_2                将常量2压入到操作栈顶
   14: istore_1                 将栈顶元素2存储到局部变量表中的第二个slot中  (slot2=2)  i=2
   15: iload_1                  将局部变量中第二个变量 (i=2) 压入到操作栈顶
   16: istore        4           将栈顶元素2存储到局部变量表中的第四个slot中  (slot4=2)
   18: iconst_3                将常量3压入到操作栈顶
   19: istore_1                 将栈顶元素3存储到局部变量表中的第二个slot中  (slot2=1)  i=3
   20: iload         4           将局部变量中第四个变量 (slot4=2) 压入到操作栈顶
   22: ireturn                   返回操作栈顶值,这时操作栈中栈顶值为2。
   23: astore_3               将栈顶元素(其它异常,Exception之外的)存储到局部变量表的第四个slot中
   24: iconst_3                将常量3压入到操作栈顶
   25: istore_1                将栈顶元素3存储到局部变量表中的第二个slot中  (slot2=3)  i=3
   26: aload_3                将局部变量中第四个变量 (其它异常) 压入到操作栈顶
   27: athrow                  抛出栈顶元素(异常信息) 无返回值

stack=1, locals=5, args_size=1

  • stack=1:操作栈的深度
  • locals=5:局部变量表中5个slot(槽位),每个slot存储能一个变量(long、double 需要两个slot存储)
    1. this变量
    2. i 变量
    3. e 变量(Exception)
    4. Exception之外异常的变量
    5. 临时存储变量(返回值从临时存储中返回的)
  • args_size=1: 方法的参数个数(该方法无参数,为什么这里args_size为1呢?因为这个是实例方法,不是静态方法,他默认会传过来当前实例的引用,也就是this变量)

字节码执行路径

通过字节码我们发现在编译成class文件的时候,已经把三种执行路径都写到class文件中了。

  1. 第一种路径
    第【1-11】行,程序正常执行顺序(无异常)
  2. 第二种路径
    第【12-22】行,程序报Exception的异常
  3. 第三种路径
    第【23-27】行,程序报Exception之外的异常

字节码大白话解释说明

第[0-1]行,代码:int i=0;
第[2-3]行,try块中代码:i=1;
第[4-5]行,遇到return时,把 i 的值临时存储起来,然后执行finally中的代码。
第[7-8] 行,finally块代码:i=3
第[9-11] 行,执行return语句,把临时存储的 i 值返回。(执行finally代码对返回值无影响)
第[12]行,catch块代码: (Exception e)
第[13-14]行,catch块代码:  i=2
第[15-16]行,遇到catch块中的return时,把 i 的值临时存储起来,然后执行finally中的代码。
第[18-19]行,finally块代码:i=3
第[20-22]行,执行catch块中return语句,把临时存储的 i 值返回。(执行finally代码对返回值无影响)
第[23]行,局部变量表中存储Exception之外的异常
第[24-25]行,finally块代码:i=3
第[26-27]行,将Exception之外的异常压入栈顶,并抛出(无返回值)

结论

通过字节码,我们发现,在try语句的return块中,return 返回的变量并不是直接返回 i 值,而是在执行finally块之前把i值存储在临时区域,当执行return时直接返回的临时区域中的值,即使在finally语句中把变量 i 的值修改了,也不会影响返回的值。

时间: 2024-10-13 22:53:11

字节码分析finally块对return返回值的影响的相关文章

JVM-String比较-字节码分析

一道String字符串比较问题引发的字节码分析 public class a { public static void main(String[] args)throws Exception{ } public static void aa(){ String s1="a";//内存在方法区的常量池 String s2="b";//内存在方法区的常量池 String s12 = "ab";//内存在方法区的常量池 String s3 = s1 +

HDFS源码分析数据块校验之DataBlockScanner

DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独的线程中,为该块池扫描.校验数据块.当一个BPOfferService服务变成活跃或死亡状态,该类中的blockPoolScannerMap将会更新. 我们先看下DataBlockScanner的成员变量,如下: // 所属数据节点DataNode实例 private final DataNode

通过字节码分析java中的switch语句

在一次做题中遇到了switch的问题,由于对switch执行顺序的不了解,在这里简单的通过字节码的方式理解一下switch执行顺序(题目如下): public class Ag{ static public int i=10; public static void main(String []args){ switch(i){ default: System.out.println("this is default"); case 1: System.out.println("

Java并发编程原理与实战八:产生线程安全性问题原因(javap字节码分析)

前面我们说到多线程带来的风险,其中一个很重要的就是安全性,因为其重要性因此,放到本章来进行讲解,那么线程安全性问题产生的原因,我们这节将从底层字节码来进行分析. 一.问题引出 先看一段代码 package com.roocon.thread.t3; public class Sequence { private int value; public int getNext(){ return value++; } public static void main(String[] args) { S

JavaScript函数概述、声明、return 返回值

一.函数的概述: 1.函数是定义一次但却可以调用或执行任意多次的一段 JS 代码.  2.函数有时会有参数,即函数被调用时指定了值的局部变量.  3.函数常常使用这些参数来计算一个返回值,这个值也成为函数调用表达式的值.(简单的说就是完成一个特定功能的代码块).  4.在 javaScript 中,Function(函数)类型实际上是对象.每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法.  5.由于函数是对象,因此函数名实际上也是一个指向函数对象的指针. 6.

MSSQL - 存储过程Return返回值

1.存储过程中不使用外部参数. 存储过程: SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: HF_Ultrastrong -- Create date: 2015年7月19日22:09:24 -- Description: 判断是否有数据,并使用Return返回值 -- =============================

4.return 返回值

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> return 返回值 </titl

C# 调用存储过程操作 OUTPUT参数和Return返回值

本文转载:http://www.cnblogs.com/libingql/archive/2010/05/02/1726104.html 存储过程是存放在数据库服务器上的预先编译好的sql语句.使用存储过程,可以直接在数据库中存储并运行功能强大的任务.存储过程在第一应用程序执行时进行语法检查和编译,编译好的版本保存在高速缓存中.在执行重复任务时,存储过程可以提高性能和一致性.由于存储过程可以将一系列对数据库的操作放在数据库服务器上执行,因而可以降低Web服务器的负载,提高整个系统的性能. 1.创

C#获取存储过程的 Return返回值和Output输出参数值

一.不用SQLHelper.cs等帮助类 1.获取Return返回值 程序代码 存储过程Create PROCEDURE MYSQL  @a int,  @b intAS  return @a + @bGOSqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalSqlServer"].ToString());conn.Open();SqlCommand MyCommand =