Java面试笔试指南(五)---异常处理、输入输出流、内存管理

1、fianlly代码块

在Java异常处理中,finally块的作用就是为了保证无论出现什么情况,finally块里的代码一定会被执行,由于程序执行了return语句就意味着结束对当前函数的调用并跳出这个函数,因此finally代码块在return之前执行,如果在finally中有return语句,那么会覆盖其他return语句

return语句在返回时,该方法体中定义的变量已经不存在了,所以return不是直接返回该变量而是复制一份然后在返回,在返回前,先将要返回的值存储在一个位置,然后再执行finally代码块,因此对于基本类型的数据,在finally代码块中改变return的值,对返回值没有任何影响,对于引用类型的数据会有影响

2、异常处理的原理

异常是指程序运行时(非编译时)所发生的非正常情况或错误,当程序违反语义规则时,JVM就会将出现的错误表示为一个异常并抛出;异常处理的目的是为了提高程序的安全性和鲁棒性,Java把异常当做对象处理

所有异常的基类(java.lang.Throwable),异常分为2类:Error和Exception

运行时异常和普通异常:

  • Error表示程序在运行期间出现了非常严重的错误,并且该错误是不可恢复的,由于这属于JVM层次的严重错误,因此这种错误或导致程序终止执行
  • Exception表示可恢复的异常,是编译器可以捕获到的,包括检查异常和运行时异常

检查异常:所以继承自Exception并且不是运行是运行时异常的异常都是检查异常,这种异常发生在编译阶段,Java编译器强制去捕获这些异常(即,把可能会出现这种异常的代码放在try-catch块中)

检查异常一般在以下情况下使用:

  1. 异常的发生并不会导致程序出错,进行处理后继续执行后续的操作
  2. 程序依赖于不可靠的外部条件

运行时异常:编译器没有强制对其进行捕获并处理,如果不对这种异常进行处理,当出现这种异常时,会由JVM来处理;出现运行时异常后,系统会把异常一直往上抛,直到遇到处理代码,如果没有处理代码,就抛到最上层,多线程中用Thread.run()方法抛出,单线程中用main()方法抛出;抛出异常后,如果是多线程,则线程退出,如果是单线程,则程序退出

异常处理时的注意点

  1. Java异常处理用到了多态,如果在异常处理中先捕获了基类,再捕获子类,那么子类的代码块永远不会被执行,因此先捕获子类,在捕获基类
  2. 尽早抛出异常,同时对捕获的异常进行处理,或者从错误中恢复或者让程序继续执行
  3. 根据实际需求自定义异常类
  4. 异常能处理就处理,不能处理就抛出,最终没有处理的异常,JVM会处理

3、Java IO流的实现机制

在Java中,输入输出都被称为抽象的流(流可以被看做一组有序的字节集合),即数据在两设备之间的传输;流的本质是数据传输,根据处理数据类型的不同,流可以分为字节流和字符流;

字节流:以字节(8bit)为单位,包含2个抽象类InputStream和OutputStream
字符流:以字符(16bit)为单位,根据码表映射字符,一次可以读多个字节包含2个抽象类Reader和Writer

字节流和字符流的主要区别:字节流在处理数据时不会用到缓存,字符流用到缓存

4、Java Socket

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一段称为一个Socket,在Java中Socket分为:面向连接的Socket通信协议(TCP)和面向无连接的Socket通信协议(UDP);任何一个Socket都是由IP地址和端口号唯一确定的

基于TCP的通信过程:

  1. Server端Listen指定的端口是否有连接请求
  2. Client端向Server端发出Connect请求
  3. Server端向Client端发出Accept消息

会话建立后,Server和Client都可以通过Send和Write等方法与对方通信

Socket的生命周期分为三个阶段:

  • 打开Socket
  • 使用Socket收发数据
  • 关闭Socket

在Java中,ServerSocket作为Server,Socket作为Client

5、Java NIO

在非阻塞IO出现之前,Java是通过传统的Socekt来实现基本的网络通信功能,经常会出现阻塞(暂停一个线程的执行以等待某个条件发生)的情况;NIO通过Selector、Channel和Buffer来实现非阻塞的IO操作

客户端和Selector之间通过双向的channel来进行通信,Selector实现了用一个线程来管理多个通道(Selector对所有Channel进行轮询访问,使用轮询的方式在处理多线程的请求是不需要上下文的切换)

6、Java序列化

Java提供2中对象持久化的方式:序列化和外部序列化

序列化(Serialization):在分布式环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式在网络上传输,序列化是一种将对象以一连串字节描述的过程,用于解决在对独享进行读写操作时所引发的问题

实现序列化的类都必须实现Serializable接口,这是一个标识接口:

  1. 使用一个输出流(如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象
  2. 使用该对象的writeObject(Object obj)方法将obj对象写出(即保存其状态)
  3. 要恢复时使用其对应的输入流

序列化的特点:

  • 如果一个类能被序列化,那么它的子类也能被序列化
  • static(代表类的成员),transient(代表它的值不需要维持是对象的临时数据),被声明为这2种类型的数据成员是不能被序列化的

由于序列化的使用会影响系统的性能,如果不是必须使用,尽可能不使用

在以下情况可以使用:

  1. 需要通过网络来发送对象
  2. 对象的状态需要被持久化到数据或文件中
  3. 序列化能实习深复制,可以复制引用的对象

反序列化:将流转换为对象,在序列化和反序列化的过程中,通过serialVersionUID来判断类的兼容性

自定义serialVersionUID的优点:

  1. 提高程序运行效率(serialVersionUID通过计算活动,显示声明可以省略计算过程)
  2. 提高程序在不同平台的兼容性(各个平台编译器计算方式可能不同,serialVersionUID可能不同,显示声明避免这个问题)
  3. 增强程序各个版本的可兼容性(类修改后serialVersionUID也会变化,这样在修改前序列化的文件在修改后将不能反序列化)

外部序列化:比较难的样子 , 实现Externalizable接口

7、System.outp.rintln()方法

默认接受一个字符串类型的变量作为参数,在使用时可以传递任意能够转换为String类型的变量作为参数

输入参数为对象时,会调用该对象的toString()方法

8、JVM加载class文件的原理

Java语言是一种具有动态性解释型语言,类(class)只有被加载到JVM中后才能运行,当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成一个完成的Java程序,这个加载过程由类加载器(类加载器也是一个类,实质是把类文件从硬盘读取到内存中)完成

类加载方式分为显式和隐式:

  • 隐式加载:程序在使用new等方式创建对象时,会隐式调用类的加载器把对应的类加载到JVM中
  • 显式加载:通过直接调用class.forName()方法来把所需要的类加载到JVM中

在Java中,类的加载是动态的,并不会一次性将所有类全部加载后在运行,而是保证程序运行的基础类完全加载到JVM中,其他类在需要时才被加载;

类的类型类加载器功能
系统类Bootstrap Loader负责加载系统类(jre/lib/rt.jar的类)
扩展类ExtClassLoader负责加载扩展类(jar/lib/ext/*.jar的类)
自定义类AppClassLoader负责加载应用类(classpath指定的目录或jar中的类)

实现原理:通过委托的方式实现,当有类需要被加载时,类加载器会请求父类来完成这个载入工作,父类会使用其自己的搜索路劲来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类

类加载的主要步骤:
1 、装载:根据查找路径找到相对应的class文件,然后导入
2、链接:
2-1、检查:检查待加载的class文件的正确性
2-2、准备:给类中的静态变量分配存储空间
2-3、解析:将符号引用转换成直接引用(可选)
3、初始化:对静态变量和静态代码块执行初始化工作

9、垃圾回收(GC)

垃圾回收主要是回收程序中不再使用的内存,主要完成3项任务:

  1. 分配内存
  2. 确保被引用对象的内存不被错误地回收
  3. 回收不再被引用的对象的内存空间

垃圾回收提高了生产效率,保证程度的稳定性,垃圾回收器必须跟踪内存的使用情况,释放没有用的对象,完成内存的释放后要处理堆中的碎片,这些操作增加JVM的负担,从而降低程序的执行效率;垃圾及回收器使用有向图来记录和管理堆内存中的所有对象

垃圾回收算法:

  1. 引用计数算法:在堆中对每个引用都有一个引用计数器,无法解决相互引用的问题
  2. 追踪回收算法:维护一个对象引用图,从根节点开始遍历,同时标记遍历到的对象,遍历结束,未被标记的对象会被回收
  3. 压缩回收算法:把堆中活动的对象移动到堆中一端,同时对堆中的碎片进行了整理,虽然简化了消除碎片的操作,但是每次操作是性能损失大
  4. 复制回收算法:把堆分成2个大小相同的区域,在任何时刻只有其中的一个区域被使用,直到整个区域被用完,此时垃圾回收器中断程序执行,通过遍历把所有活动的对象复制到另一个区域,复制过程紧挨在一起,消除内存碎片,结束后程序继续运行,不断重复这个过程
  5. 按代回收算法:复制回收算法每次执行时们所有处于活动状态的对象都要被复制,效率低下;把堆分为很多子堆,每一个子堆视为一代,算法在运行过程中优先收集年幼的对象,如果一个对象经过多次收集仍然存活,把这个对象转移到高级堆里,减少对其的扫描次数

10、内存泄露

内存泄露是指一个不再被程序使用的对象或变量还在内存中占有存储空间

在Java中判断一个内存空间是否符合垃圾回收的标准有2个:

  1. 给对象赋了空置null,以后在没有被使用过
  2. 给对象赋予了新值,重新分配了内存空间

一般内存泄露存在2种情况:

  1. 堆中申请的空间没有被释放
  2. 对象已不再使用,但还在内存中保留着

容易引起内存泄露的原因:

  1. 静态集合类(这些类创建的容器生命周期和程序一样长,容器中对象使用结束并不会被释放)
  2. 各种连接(连接未关闭,造成大量对象无法被回收)
  3. 监听器(释放对象时监听器未删除)
  4. 变量不合理的作用域(变量定义的作用范围大于使用范围)
  5. 单例模式可能造成内存泄露(单例模式中存在对对象的引用,单例模式生命周期与程序一样,所以其中的对象引用不能被释放)

11、堆与栈的区别

在Java中堆与栈都是内存中存放数据的地方

内存存放东西内存管理方式
变量(基本数据类型和引用类型)变量出了作用域会自动释放压栈和弹栈,以栈帧为基本单位管理程序的调用关系,有函数调用时也会压入栈中
引用类型变量的对象,通过new等方法创建运行时创建的对象,垃圾回收器回收
常量池如String对象,通过new等方法创建运行时创建的对象,垃圾回收器回收

JVM是基于堆栈的虚拟机,每个Java程序都运行在一个单独的JVM实例上,每个实例唯一对应一个堆,一个Java程序内的多个线程也运行在同一个JVM实例上,这些线程之间共享堆内存(多线程范围堆数据时要进行同步)

在堆中产生了一个数组或对象后,可以在栈中定义一个特殊的变量,当栈中这个变量的取值等一数组或对象在堆内存中的首地址,栈中的这个变量就变成了数组或对象的引用变量(引用变量相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中引用变量来访问堆中数组或对象)

堆中主要存放对象,栈主要用来执行程序
栈的存取速度快,但是栈的大小和生存期必须是确定的,缺乏灵活性
对可以在运行时动态分配内存,生存期不用提前告诉编译器,但存取速度慢

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值