您的位置:首页 > 编程语言 > Java开发

Java Notes: 从内存看Java,异常处理

2016-10-07 10:04 232 查看
以以下程序作为例子:

public class CustomerTest {
public static void main(String[] args) {
Customer[] list = new Customer[5];
Customer c = new Customer("Jeb", "Bush");
list[0] = c;
list[1] = new Customer("George", "Bush");
...
} }


Java程序以main函数作为入口,看到关于Customer的new关键字,找Class Customer的定义,然后在字节码(前端编译器之后,后端解释器和编译器前)中:



其中,<cinit>部分是初始化静态变量的字节码。初始化一个类时,首先执行的是这部分。这就是为什么之前在文章《初始化与清理部分》提到的,Java的初始化顺序是:静态变量、类普通变量、构造函数。引用[1]的说法:<init> is the (or one of the) constructor(s) for the instance, and non-static field initialization. 所以,普通类成员变量和构造函数是交给<init>处理的。

以下面代码为例:

class X {

static Log log = LogFactory.getLog(); // <clinit>

private int x = 1;   // <init>

X(){
// <init>
}

static {
// <clinit>
}

}


接着,字节码里面,存储的就是各种方法的字节码了。

关于Description: 

接下里,讲一下description的概念。在java当中,description会实现多态(dynamic binding)的保证:

每一个类的对象创建之后,在堆内存里的存储内容如图所示:



这里假设这个Customer有三个普通类变量:ID(int), First Name (String) 和 Last Name (String)。那么,前面的那个引用是指向哪里?就是对应的Customer类的description。Description的每个格子(引用)实质是指向这个类的整个“族谱”的引用。如下图所示:



其指向的是其本身和父类的字节码。遇到一个方法调用,如果,引用是本身的类的,首先在本身的字节码内寻找对应的方法名,如果没有找到,再到父类中寻找。

那么,如果引用的父类的,就从父类的字节码开始找起。多态的实现基础便是如此。

讲讲方法的栈内容(Stack):

在Java当中,每一个线程(注意是线程,不是进程)都有自己的stack frame。每个stack frame里面存的都是方法的一些local变量。(顺便提一句,这是和Linux的shell脚本语言是相反的,在shell的函数的变量默认是全局的,这样很容易引起同名变量的篡改。需要使用local使得其中的变量变为本地变量。)

如下图所示(引用自CS:APP):



这幅图用的是x86-32的习惯,调用函数前,把函数对象的参数按顺序依次push到上一个stack frame的中。然后,call下一个函数的时候,call函数自动把返回地址(下一个指令在内存中的地址)存在栈里面,然后再跳到对应的函数的开始指令地址。

对于x86-64的机器,习惯使用%rdi, %rsi, %rdx, %rcx, r8, %r9等作为参数变量寄存器,在call下一个函数前,用mov指令给对应的寄存器赋值即可。

Saved register就是进入了下一个函数之后,进行callee save的一些寄存器的值。在子函数里调用push实现。

This 和 super的区别:

static方法和普通类方法最大的区别在于,类方法的栈区域当中存在this引用的空间。这里顺便也说一句,相比于this,另外一个特别的关键词super则不是这样的。super并不是存在于方法的引用,其只是一个和编译器的约定暗号。一旦看到super调用的方法,自动从description的父类方法字节码可以搜起。

异常处理的几个需要知道的点:

我们之前程序出错时,在控制台看到的信息其实是程序把Runtime Exceptions抛出来了,由于我们并没有写任何catch RunTime Exception的代码,所以JVM默默的接受了这个Exception。然后,JVM对于所有exception的默认行为都是e.printStackTrace()。

咦,之前不是说抛异常应该是跑checked exceptions吗?Runtime exceptions和Runtime Errors不应该是Unchecked的吗?

是的,理论上来说,声明throws和实际throw(注意两者不同,一个只是声明,没有处理;后者是直接处理)的都应该是Checked的异常。其中有两个原因:

1)客观原因:Checked异常之所以被称为被检查的,是因为它是编译器会检查你这段代码可能会产生什么异常,如果发现有被检查的异常可能存在你的代码,编译器就是强制你要不就声明,要不就在代码当中自己处理;

2)主观原因:Checked异常产生的原因是库程序员写的代码被别人调用时,本身可能存在的问题。而这些问题一般能用catch回复。而Unchecked的exception更多是调用者的代码的问题。而且就算catch到了,也没有什么处理的可能。必须重新改代码才能正常运行。

但是,runtime exceptions是可以被catch的。

插一句题外话:JVM默认std error只输出最多1024行。递归时如果stack overflow时产生了1024行不要以为只被调用了1024次。可以通过java -XX设置,远不止1024次调用。

下面是关于Exceptions的一些实用的代码技巧:

public static int factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException(”Negative nu...
}
if (n == 1 || n == 0) return 1;
return n * factorial(n-1);
}


在new时,在构造函数中插入字符串,是这个exception的message。可以在catch到exception时,使用:

catch(IllegalArgumentException e){
System.out.println(e.getMessage());
}


来查看。

另外,子类的exceptions应该在其父类之前,否则never reach。

另外,可以使用finally语句来关闭一些资源。因为它一定会被执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息