Java SE 面试题05


1. Error与Exception区别?

Error和Exception都是java错误处理机制的一部分,都继承了Throwable类。

Exception表示的异常,异常可以通过程序来捕捉,或者优化程序来避免。

Error表示的是系统错误,不能通过程序来进行错误处理。

2. 使用Log4j对程序有影响吗?简述Log4j日志的级别?

有,log4j是用来日志记录的,记录一些关键敏感的信息,通常会将日志记录到本地文件或者数据库中。记录在本地文件中,会有频繁的io操作,会耗费一些系统资源。记录在数据库中,会频繁地操作数据库表,对系统性能也有一定的影响。但是为了程序安全以及数据的恢复或者bug的跟踪,这点资源消耗是可以承受的。

由低到高:debug、info、warn、error

3. 除了使用new创建对象之外,还可以用什么方法创建对象?

​ 反射

*4. Java反射创建对象效率高还是通过new创建对象的效率高?

​ 通过new效率高,通过反射创建对象需要先找查找类资源,再使用类加载器创建,这个过程需要消耗时间,而使用new,这些步骤是在编译阶段由编译器完成的。

*5. 常用io类有那些?

File FileInputSteam,FileOutputStream BufferInputStream,BufferedOutputSream PrintWrite FileReader,FileWriter BufferReader,BufferedWriter ObjectInputStream,ObjectOutputSream

6. 字节流与字符流的区别?

​ 以字节为单位输入输出数据,字节流按照8位传输;

​ 以字符为单位输入输出数据,字符流按照16位传输。

7. final、finalize()、finally?

final为关键字: final为用于标识常量的关键字,final标识的关键字存储在常量池中(在这里final常量的具体用法将在下面进行介绍);

finalize()为方法:finalize()方法在Object中进行了定义,用于在对象“消失”时,由JVM进行调用用于对对象进行垃圾回收,类似于C++中的析构函数;用户自定义时,用于释放对象占用的资源(比如进行I/0操作);

finally为区块标志,用于try语句中: finally{}用于标识代码块,与try{}进行配合,不论try中的代码执行完或没有执行完(这里指有异常),该代码块之中的程序必定会进行;

*8. 线程同步的方法?

 wait(): 让线程等待。将线程存储到一个线程池中。

​ notify():唤醒被等待的线程。通常都唤醒线程池中的第一个。让被唤醒的线程处于临时阻塞状态。

​ notifyAll(): 唤醒所有的等待线程。将线程池中的所有线程都唤醒。

*9. 线程与进程的区别?

进程是系统进行资源分配和调度的一个独立单位,线程是CPU调度和分派的基本单位

进程和线程的关系:

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  3. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
  4. 线程是指进程内的一个执行单元,也是进程内的可调度实体。

线程与进程的区别:

  1. 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
  2. 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
  3. 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
  4. 系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致创建或撤销进程的开销远大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些。

10. &和&&的区别?

​ &是位运算符。&&是布尔逻辑运算符,在进行逻辑判断时用&处理的前面为false后面的内容仍需处理,用&&处理的前面为false不再处理后面的内容。

11. 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?

​ 不会,在下一个垃圾回收周期中,这个对象将是可被回收的。

*12. 简述分代收集策略,并描述Serial GC的回收过程

弱代假设(weak generational hypothesis)和分代收集策略:

1. 大部分最近创建的对象不会存在很长时间,比如函数、代码块内部创建的局部变量。
2. 已经存活很长时间对象,在未来被释放的概率也很低,因为他们甚至有可能是static的。

串行GC和并行GC:

​ 串行,比较简单,事情一件一件按顺序做,单线程执行GC的所有工作。

​ 并行,多个线程一起负责GC的工作,通常是每个线程分担一段不重叠的部分,这样的话可以减少线程间的干扰和同步开销。

并发(Concurrent) vs Stop The World:

​ 并发,是指应用的代码在运行时,GC的工作线程同时也在工作。

​ Stop The World,顾名思义,就是GC运行时会停止所有的其他线程。

整理(Compacting) vs 不整理(Non-compacting):

​ 因为内存的分配是顺序的,但是释放不一定是顺序,这样会出现内存碎片,因为对象内存的分配必须是连续的,所以多块小内存碎片(每片大小都小于需求,但是总体容量大于需求)是无法满足一个大内存需求的。

​ Compacting型的算法,就是在GC的时候,会把获得的对象向一端移动,使对象紧凑的挨在一起,消除内存碎片带来的问题. 除了避免了碎片问题外,对象的分配也变得简单多了,只需要维护一个top和end指针即可,当需要分配的对象大小加上top不超过end时,直接返回当前的top值,然后将top往end端移动对象大小的字节偏移即可. 即使在并发情况下,也只需要CAS(compare and swap)指令就能完成. 不过凡事有利有弊,因为要移动对象,对象的位置发生了改变,必须同步修改引用对象的那些位置,为了维持一致性,通常需要在GC安全点的环境下移动对象,当要整理的heap很大时,将耗费较长的时间。

​ Non-compacting型的算法就是另一种,不整理的,空闲区域一般通过链表进行管理. 相比于Compacting的方式,需要维护额外的链表结构,并且分配也变得复杂,同时也可能引发内存碎片问题。另一方面,由于不移动对象,节省了内存copy的时间,同时对象的释放可以和应用的代码一起执行,这是它的一个优势。

JVM中的heap被分成年轻代和老年代,年轻代会执行更频繁的GC,老年代的GC则很少触发,但是触发后运行时间更长。同时,年轻代和老年代可以使用不同的策略,比如串行或者并行,并发或者stop the world,整理或者不整理。>

Serial GC(串行GC)

串行GC是hotspot中最简单的一种GC,不过麻雀虽小五脏俱全。除了速度相较其他几种GC比较慢之外,完成的功能是一样的:回收不再使用的对象,为新的对象分配空间

Serial GC 的内存分布:

Serial GC的heap采用的是分代分布,默认情况下,年轻代与老年代的大小之比为1:2,如下图:

总体上heap分为年轻代(YoungGen)和老年代(OldGen)两个大块。而在年轻代中,又进一步划分成两个部分: Eden Space和两块Survivor Space(存活区),S1和S2. 默认情况下,Eden区和Survivor区(S1 + S2)的比例为8:2。存活区用于存放那些在年轻代GC中没有被回收,但是GC年龄没有达到老年代阈值的对象,简单来说就是一块通向老年代的过渡区。每次年轻代GC中存活下来的对象,GC年龄将加1,当达到年龄要求(默认15),则将该对象移动到老年代中。之所以有两块,主要是方便对象的回收和分配,在年轻代GC中,其中的一块将扮演From的角色,一块扮演To的角色,并且每次GC后,这两个角色是互相交换的。To区用来保存本次GC中从Eden区中和From区(第一次GC时,From是空的)中存活下来的对象,然后Eden区和From区清空,下一次GC中,清空的From区成为To区,To区成为From区。

Serial GC年轻代回收:

​ 初始状态

​ 标记&移动

​ 修正指针

​ 完成&清空

Serial GC老年代回收:

​ 初始状态

​ 标记

​ 计算&整理

​ 移动

​ 完成


文章作者: 银色回廊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 银色回廊 !
评论
  目录