Java实践(二)---异常、断言、日志和调试

1.可能造成程序崩溃的错误输入,Java使用一种称为异常处理(exception handing)的错误捕获机制处理
2.使用断言来有选择的启用检测
3.Java日志框架:当程序出错时,记录下出现的问题,

1.错误处理

在Java程序运行期间出现了一个错误,可能是由于文件包含了错误信息,或者网络连接出现了问题,或因为使用了无效的数组下标,或试图使用一个没有被赋值的对象引用而造成的

如果因为出现错误而使得某些操作没有完成;程序应该:

返回到一种安全状态,并能够让用户执行一些其他的命令
允许用户保存所有操作的结果,并以适当的方式终止程序

异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器
为了能够在程序中处理异常情况,必须研究程序中可能会出现的错误和问题,以及哪类问题需要关注

1.用户输入错误
2.设备错误(硬件问题)
3.物理限制(磁盘满了,可用存储空间已被用完)
4.代码错误

在Java中,如果某个方法不能采用正常的途径完成它的任务,就可以通过另外一个路径退出方法,在这种情况下,方法并不返回任何值,而是抛出(throw)一个封装了错误信息的对象【这个方法立即退出,并不返回任何值,调用这个方法的代码也将无法继续执行,而是,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)】

1.异常分类

在Java中异常对象都是派生于Throwable类的一个实例,如果Java中内置的异常类不能够满足需求,用户可以创建自己的异常类

Java实践(二)---异常、断言、日志和调试_第1张图片

所有异常都是由Throwable继承而来,在下层分解为Error【未检查异常】和Exception,Exception分为RuntimeException【未检查异常】和IOException【已检查异常】

编译器将核查是否为所有的已检查异常提供了异常处理器

Error:Java运行时系统内部的错误资源耗尽错误(应用程序不应该抛出这种类型的对象)
RuntimeException:由程序导致的错误【逻辑错误】
IOException:程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常

派生于RuntimeException【编写代码是的逻辑错误】:

1.错误的类型转换
2.数组访问越界
3.访问空指针

不派生于RuntimeException的其他异常:

1.试图在文件尾部后面读取数据
2.试图打开一个不存在的文件
3.试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在

如果出现RuntimeException那就一定是你的问题!!!

2.声明已检查异常

如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常;一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误

例如:一段读取文件的代码知道有可能读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException类的异常

方法应该在其首部声明所有可能抛出的异常

例如:

public FileInputStream(String name)throws FileNotFoundException

这个声明表示构造器将根据给定的String参数产生一个FileInputStream对象,但是也有可能抛出一个FileNotFoundException异常,如果发生这种糟糕的情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象;如果这个方法真的抛出这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException对象

在自己编写方法时,不必将所有可能的异常都进行声明
什么时候需在方法中用throws子句声明异常,什么异常必须使用throws子句声明,有如下4中情况应该抛出异常:

1.调动一个已检查异常的方法,例如,FileInputStream构造器
2.程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常
3.程序出现错误,例如,数组访问越界
4.Java虚拟机和运行时库出现的内部错误

如果出现1或2,必须告诉调用这个方法的程序员有可能抛出异常,如果没有处理器捕获这个异常,当前执行的线程就会结束

对于那些可能被别人使用的Java方法,应该根据异常规范(exception specification),在方法的首部声明这个方法可能抛出的异常;如果一个方法有可能抛出多个已检查异常,那么必须在方法的首部列出所有的异常类,每个异常类之间用逗号隔开;不需要声明Java的内部错误,(即从Error继承的错误),也不应该声明从RuntimeException继承的那些未检查异常

一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控(Error)要么就应该避免发是(RuntimeException)

如果在子类中覆盖了超类的一个方法,子类方法中声明的已检查异常不能比超类方法中声明的异常更通用(即子类方法中可以抛出更特定的异常,或者根本不抛出异常)【如果超类方法没有抛出已检查异常,子类也不能抛出已检查异常】

如果类中的一个方法声明将会抛出一个异常,而这个异常是某个特定类的实例时,这个方法就有可能抛出一个这个类的异常,或者这个类的任意一个子类的异常

在Java中,没有throws说明符的方法将不能抛出任何已检查异常

3.如何抛出异常

对于一个已存在的异常,将其抛出,在这种情况下:

1.找到一个合适的异常类
2.创建这个类的对象
3.将对象抛出

一旦方法抛出了异常,这个方法就不可能返回到调用者(也就是说,不必为返回的默认值或错误代码担忧)

4.创建异常类

在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题,在这种情况下,创建自己的异常类;定义一个派生于Exception的类,或者派生于Exception子类的类【定义的类应该包含2个构造器,一个默认构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息,这在调试中非常有用)】

class FileFormatException extends IOException
{
    public FileFormatException(){}
    public FileFormatException(String gripe)
    {
        super(gripe);
    }
}   

2.捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容

想要捕获一个异常,必须设置try/catch语句块:

try
{
    code
    more code
}
catch(ExceptionType e)
{
    handler for this type
}

如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么:

1.程序将跳出try语句块的其余代码
2.程序将执行catch子句中的处理器代码

如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出

相比于采用try/catch语句,通常,最好的选择是什么也不做,将异常传递给调用者,通过声明这个方法可能会抛出一个Exception

编译器严格的执行throws说明符如果调用了一个抛出已检查异常的方法,就必须对它进行处理,或者将它继续进行传递

通常应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递【如果想要传递一个异常,就要在方法的首部添加一个throws说明符】

仔细阅读JavaAPI文档,以便知道每个方法可能会抛出哪种异常,然后在决定是自己处理还是添加到throws列表中

例外:
如果编写了一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个已检查异常(不允许子类的throws说明符中出现超过超类方法所列出的异常类型范围)

1.捕获多个异常

在一个try语句块中,可以捕获多个异常类型,并对不同类型的异常做出不同的处理,每个异常使用一个catch子句,异常对象可能包含与异常本身有关的信息,要想获得对象的更多信息,可以试着使用e.getMessage()得到详细的错误信息,或使用e.getClass().getName()得到异常对象的实际类型

假设对同一个异常的处理动作是一样的,那么可以合并catch子句catch(Exception1 | Exception2 e)只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性【异常变量隐含为final变量】

2.再次抛出异常与异常链

在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型

try
{
    access the database
}
catch(SQLException e)
{
    Throwable se = new ServletExceptiom("database error");
    se.initCause(e);
    throw se;
}

当捕获到异常时,就可以使用下面这条语句重新得到原始异常Throwable e = se.getCause();这样可以抛出子系统的高级异常,而不会丢失原始异常的细节

有时只是想记录一个异常,再将它重新抛出,而不做任何改变:

try
{
    access the database
}
catch(Exception e)
{
    logger.log(level,message,e);
    throw e;
}

3.finally子句

当代码抛出一个异常时,就会终止方法中剩余的代码,并退出这个方法的执行,如果方法获得一些本地资源,并且只有这个方法自己知道,如果这些资源在退出之前必须收回,那么就会产生资源回收问题;一种解决方案是捕获并重新抛出所有的异常,但是这样的话需要在2个地方清除所分配的资源,一个在正常代码中,另一个在异常代码中

不管是否有异常被捕获,finally子句中的代码都被执行

InputStream in = new FileInputStream(...);
try
{
    //1
    code that might throw exception
    //2
}
catch(IOException e)
{
    //3
    show error message
    //4
}
finally
{
    //5
    in.close(); 
}
//6

在上面这段代码中有3种情况会执行finally子句:

1.代码没有抛出异常:执行标注如下1-2-5-6
2.抛出一个在catch子句中捕获的异常:1.如果catch子句捕获这个异常,程序将执行try语句块之后的第一句,执行标注如下1-3-4-5-6;2.如果catch子句抛出这个异常,异常将抛回这个方法的调用者,执行标注如下:1-3-5
3.代码抛出了一个异常,但这个异常不是由catch子句捕获的:这种情况下,程序将执行try语句块中的所有语句,直到有异常被抛出为止,此时将跳过try语句块中剩下的代码,然后执行finally子句中的语句,并将异常抛给方法调用者,执行标注如下:1-5

try语句可以只有finally子句没有catch子句,无论在try语句中是否遇到异常,finally子句都会被执行

InputStream in = ...;
try
{
    try
    {
        code that might throw exception
    }
    finally
    {
        in.close();
    }
}
catch(IOException e) 
{
show error message
}

内层的try语句只有一个职责,确保关闭输入流,外层try语句块也只有一个职责,确保报告出现的错误【这种设计方法不仅清楚,而且会报告finally子句中出现的错误】

假设利用return语句从try语句块中退出,在方法返回前,finally子句的内容将被执行,如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值

有时候finally子句也会带来麻烦:

InputStream in = ...;
try
    {
        code that might throw exception
    }
finally
{
    in.close();
}

假设在try语句块中的代码抛出一个非IOException的异常,这些异常只有这个方法的调用者才能处理,执行finally语句块,并调用close方法,而close方法本身有可能抛出IOException,当出现这种情况,原始的异常将会丢失,转而抛出close方法的异常【如果想要重新抛出原异常,代码会非常复杂,如下】

InputStream in = ...;
Exception ex = null;
try
{
    try
    {
        code that might throw exception
    }
    catch(Exception e)
    {
        ex = e;
        throw e;
    }
}
finally
{
    try
    {
        in.close();
    }
    catch(Exception e)
    {
        if(ex == null)throw e;
    }
}

4.带资源的try语句

代码模式:

open a resource
try
{   
    work with the resource
}
finally
{
    close the resource
}

带资源的try语句(try-with-resource)的最简形式:

try(Resource res = ...)
{
    work with res
}

try块退出时,会自动调用res.close(),例如:

try(Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words")))
{
    while(in.hasNext())
        System.out.println(in.next());
}       

这个块正常退出或存在异常时,都会调用in.close()方法
当调用多个资源:

try(Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"));Printwriter out = new Printwriter("out.txt"))
{
    while(in.hasNext())
        out.println(in.next().toUpperCase());
}   

不论这个块如何退出,in和out都会关闭

如果try语句和close方法都抛出异常,带资源的try语句很好的处理这种情况,try语句的异常被抛出,close方法的异常会“被抑制”,这些异常将自动捕获,并由addSuppressed方法增加到原来的异常中,如果对这些异常感兴趣,可以调用getSuppressed方法,它会得到从close方法抛出并被抑制的异常列表

只要关闭资源,就要尽可能使用带资源的try语句

5.分析堆栈跟踪元素

堆栈跟踪(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置(Java程序正常终止,没有捕获异常时,这个列表会显示出来)

1.可以调用Throwable类的printStackTrace方法访问堆栈跟踪的文本描述信息
2.可以使用getStackTrace方法,它会得到StackTraceElement对象的一个数组
3.静态Thread.getAllStackTrace方法,可以产生所有线程的堆栈跟踪

2的例子:

Trowable t = new Trowable();
StackTraceElement[] frames = new StackTraceElement();
for(StackTraceElement frame:frames)
    analyze frame

3的例子:

Map<Thread, StackTraceElement[]> map = Thread.getAllStackTrace;
for(Thread t : map.keySet())
{
    StackTraceElement[] frames = map.get(t);
    analyze frames;
}

3.使用异常机制的技巧

1.异常处理不能代替简单的测试

执行简单测试花费的时间比捕获异常少很多

使用异常的基本原则:只在异常情况下使用异常机制

2.不要过分的细化异常

每条语句都封装在独立的try语句块中,将导致代码量的急剧膨胀

3.利用异常层次结构

1.不要只抛出IOException,应该寻找更加适合的自子类或者创建自己的异常类
2.不要只捕获Throwable,否则,会使程序代码更难读、更难维护
3.考虑已检查异常和未检查异常的区别
4.将一种异常转换为另一种更合适的异常时不要犹豫

4.不要压制异常

编译器会对所有调用这个方法的方法进行异常处理的考虑,如果认为异常非常重要,就应该对它们进行处理

try
{
    code that threatens to throw checked exceptions 
}
catch(Exception e)
{}

这段代码可以通过编译,除非发生异常,否则它将可以正常运行,即使发生了异常也会被忽略

5.在检测错误时,“苛刻”要比放任更好

例如,当栈为空时,在出错的地方抛出一个EmptyStackException比在后面抛出一个NullPointerException更好

6.不要羞于传递异常

传递异常比捕获异常更好,让高层次的方法通知用户发生了错误,或者放弃不成功的命令更合适

规则5、6归纳为“早抛出,晚捕获”

4.使用断言

断言机制允许在测试期间向代码中插入一些检查语句,当代码发布时,这些插入的检测语句就会被自动地移走
关键字assert的2种形式:

assert条件
assert条件:表达式(表达式部分的唯一目的是产生一个消息字符串)

这2种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常,在第二种形式中,表达式被传入AssertionError的构造器,并转换成一个消息字符串

在Java中,条件不会自动的成为错误报告的一部分,如果希望看到这个条件,必须将它以字符串的形式传递给AssertionError对象,如:assert x >= 0 : “x >= 0”

1.启用和禁用断言

在默认情况下,断言被禁用;在程序运行时用-enableassertion或-ea选项启用,如:java -ea MyApp,使用-disableassertion或-da禁用断言

启用或禁用断言不必重新编译程序,这是类加载器(class loader)的功能

2.使用断言完成参数检查

在Java中,给出3中处理系统错误的机制:

1.抛出一个异常
2.日志
3.使用断言

使用断言的情况:

断言失败是致命的、不可恢复的错误
断言检查只用于开发和测试阶段(确定程序内部的错误位置)

3.为文档假设使用断言

断言是一种测试和调试阶段所使用的战术性工具,日志记录是一种在程序的整个生命周期都可以使用的策略性工具

5.记录日志

日志API的优点:

1.可以很容易地取消全部日志记录,或仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易
2.可以很简单地禁止日志记录的输出,因此,将这些代码留在程序中的开销很小
3.日志记录可以被定向到不同的处理器,用于在控制台中显示或用于存储在文件中
4.日志记录器和处理器都可以对记录进行过滤,过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录项
5.日志记录可以采用不同的方式格式化,如纯文本或XML
6.应用程序可以使用多个日志记录器,它们使用类似包名的这种具有层次结构的名字,例如:com.mycompany.myapp
7.在默认情况下日志系统的配置由配置文件控制,如果需要的话,应用程序可以替换这个配置文件

1.基本日志

日志系统管理者一个名为Logger.global的默认日志记录器,可用System.out替换它,并调用info方法记录日志信息:Logger.global().info("File—>Open menu item selected");默认情况下这条记录显示如下内容:

May 10,2003 10:12:15 PM LoggingImageViewer fileOpen
INFO:File—>Open menu item selected

自动包含了时间、调用的类名和方法名,如果在相应的地方(如main开始)调用Logger.global().setLevel(Level.OFF);将会取消所有的日志

2.高级日志

在一个专业的应用程序中,不要将所有的日志都记录到一个全局日志记录器中,而是可以自定义日志记录器,调用getLogger可以创建或检索记录器:private static final Logger myLogger = Logger.getLogger("com.mucompany.myapp");

日志记录器的父与子之间将共享某些属性

有7个级别的日志记录器:

1.SEVERE
2.WARNING
3.INFO
4.CONFIG
5.FINE
6.FINER
7.FINEST

在默认情况下,只记录前3个级别,也可以设置其他级别,如:Logger.setLevel(Level.FINE);

对于所有的级别有如下记录方式:

logger.warning(message);
logger.fine(message);

通过使用log方法指定级别:

logger.log(FINE,message);

默认的日志配置记录了INFO或更高级别的所有记录,要记录更低的级别,需要修改日志记录器的配置

默认的日志记录将显示日志调用的类名和方法名,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息了,可以调用logp方法获取调用的类和方法的确切位置:void logp(Level 1, String className, String methodName, String message)

记录日志的常见用途是记录那些不可预料的异常,用法如下:

if(...)
{
    IOException exception = new IOException("...");
    logger.throwing("com.myconpany.mylib.Reader","read",exception);
    throw exception;
}

调用throwing可以记录一条FINER级别的记录和一条以THROW开始的信息

try
{
    ...
}
catch(IOException e)
{
    Logger.getLogger("com.mycompany.myapp").log(Level.WARNING,"Reading image",e);
}

3.修改日志记录器配置

通过修改配置文件来修改日志系统的各种属性,默认情况下,配置文件存在于jre/lib/logging.properties,要想使用另一个配置文件,就要将java.uti.logging.config.file特性设置为配置文件的存储位置,并用下列命令启动应用程序java -DJava.util.logging.config.file=configFile MainClass

修改日志记录级别,要编辑配置文件并修改以下命令行:.level=INFO,可以通过添加以下内容来指定自己的日志记录级别:com.mycompany.myapp.level=FINE

要想在控制台看到FINE级别的消息,进行如下设置:java.util.logging.ConsoleHandler.level=FINE

4.本地化

可能希望将日志信息本地化,以便让全球的用户可以阅读
本地化日志消息时的要点:

1.本地化的应用程序包含资源包(resource bundle由各个地区的映射集合组成)中的本地特定信息
2.一个程序可以包含多个资源包,一个用于菜单,其他用于日志消息(每个资源包都有一个名字,要想将映射添加到一个资源包中,需要为每个地区创建一个文件)

在请求日志记录器时,可以指定一个资源包:Logger logger = Logger.getLogger(loggerName,"com.mycompany.logmessages");然后为日志消息指定资源包的关键字,而不是实际的消息字符串logger.info("readingFile");

通常需要在本地化的消息中增加一些参数,因此,消息应该包括占位符{0},{1}等,例如要在消息中包含文件名,就应该用下列方式包括占位符:

Reading file {0}
AchtungDatei {0} wird eingelesen

然后通过调用下面的一个方法向占位符传递具体的值:

logger.log(Level.INFO,"readingFile",fileName);
logger.log(Level.INFO,"readingFile",new Object[] {oldName,newName});

5.处理器

在默认情况下,日志记录器将记录发送到ConsoleHandler中,并由它输出到System.err流中【特别是,日志记录器会将记录发送给父处理器,而最终的处理器有一个ConsoleHandler】

对于一个要记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阈值

日志记录器配置文件设置的默认控制台处理器的日志记录级别为:java.util.logging.ConsoleHandler.level=INFO
要想记录FINE级别的日志,就必须修改配置文件中的默认日志记录级别和处理级别,还可以绕过配置文件,安装自己的处理器:

Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);

默认情况下,日志记录器将记录发送到自己的处理器和父处理器,我们的日志记录器是原始日志记录器(命名为“ ”)的子类,原始日志记录器将会把所有等于或高于INFO级别的记录发送到控制台,我们并不想2次看到这些记录,因此setUseParentHandlers设置为false

要想将日志记录发送到别的地方,就要添加其他的处理器,日志API为此提供了2个很有用的处理器:

FileHandler:收集文件中的记录
SocketHandler:将日志发送到特定的主机和端口

直接将记录发送到默认文件的处理器:

FileHandler handler = new FileHandler();
logger.addHandler(handler);

这些记录被发送到用户主目录的javan.log文件中,n是文件名的唯一编号,默认情况下,格式为XML
如果多个应用程序或一个应用程序的多个副本使用一个日志文件,应该开启append标志,应该在文件名模式中使用%u,以便每个应用程序创建日志的唯一副本

文件处理器的配置参数见书本 p502
日志记录文件模式变量见数 p502

6.过滤器

默认情况下,过滤器根据日志记录的级别进行过滤,每个日志记录器和处理器都可以有一个可选的过滤器,通过实现Filter接口并定义下列方法来自定义过滤器:boolean isLoggable(LogRecord record),在这个方法中可以按照自己的标准对日志进行分析,返回true表示这个记录应该包含在日志中

调用setFilter将一个过滤器安装到一个日志记录器或处理器中,同一时刻最多只能有一个过滤器

7.格式化器

自定义格式,扩展并覆盖下面的方法:String format(LogRecord record),在format方法中调用String formatMessage(LogRecord record)这个方法对记录中的部分消息进行格式化、参数替换和本地化应用操作

很多格式化的记录(如XML)需要在已格式化的记录前加上头部和尾部,覆盖下面的方法:

String getHead(Handler h);
String getTail(Handler h);

最后调动setFormatter方法将格式化器安装到处理器中

8.日志记录说明

1.为一个简单的应用程序,选择一个日志记录器,并把日志记录器命名为主应用程序包一样的名字
2.默认的日志配置将级别等于或高于INFO的所有消息记录到控制台
3.所有的INFO、WARNING和SEVERE的消息都将显示在控制台,因此对用户有意义的设置为这3个级别,对程序员有意义的设置为FINE

改变配置文件需要很多的工作,最好在应用程序中安装一个更适宜的默认处理器:

if(System.getProperty("java.util.logging.config.class") == null && System.getProperty("java.util.logging.config.file") == null)
{
    try{
        Logger.getLogger("").setLevel(Level.ALL);
        final int LOG_ROTATION_COUNT = 10;
        Handler handler = new FileHandler("%h/myapp.log",0,LOG_ROTATION_COUNT);
        Logger.getLogger("").addHandler(handler);
    }
    catch(IOException e)
    {
        logger.log(Level.SEVERE,"Can't create log file handler", e);
        }
    }
}       

以上代码确保将所有的消息记录到应用程序的特定文件中,将代码放再应用程序的main方法中

6.调试技巧

1.打印或记录任意变量的值System.out.println("x="+x);Logger.getGlobal().info("x="+x)
2.每个类中设置一个main方法,这样可以对每一个类进行单元测试,只要创建少量的对象,调用所有的方法,并检测每个方法是否能够正确地运行就可以了【在运行applet应用程序时,这些main方法不会被调用,运行应用程序时,虚拟机只会调用启动类的main方法】
3.Junit是一个常见的单元测试框架
4.日志代理(logging proxy)是一个子类的对象,它可以窃取方法调用,并进行日志记录,然后调用超类中的方法
5.利用Throwable类提供的printStackTrace方法,可以从任何一个异常对象中获得堆栈的情况(不一定呀通过捕获异常来生成堆栈跟踪,只要在代码的任何位置插入下面这条语句就可以获得堆栈跟踪:Thread.dumpStack()
6.一般来说,堆栈跟踪显示在System.err上,也可以利用printStackTrace(printWriter s)方法将它发生到一个文件中
7.将一个程序中的错误信息保存在一个文件中的非常有用的,错误信息被发送到System.err中,采用下面方式捕获错误流java MyProgram 2> errors.txt,要想在同一个文件中同时捕获System.err和System.out,使用这条命令java MyProgram 1> errors.txt 2>&1
8.让非捕获异常的堆栈跟踪出现在System.err中并不是一个理想的方法,可以调用Thread.setDefaultUncaughExceptionHandler方法改变非捕获异常的处理器
9.想观察类的加载过程,可以用-verbose标准启动Java虚拟机,这种方法有利于诊断类路径引发的问题
10.Xlint选项告诉编译器对一些普遍容易痴线的代码问题进行检查【书本p 515中列出】
11.Java虚拟机增加了对Java应用程序进行监控和管理的支持
12.使用jmap实用工具获得一个堆的转储,其中显示了堆中的每个对象
13.使用-Xprof标志运行Java虚拟机,就会运行一个基本的剖析器来跟踪那些代码中经常被调用的方法

你可能感兴趣的:(Java,Java实践)