Java面试题(2) – 体系结构

Java面试题(2) – 体系结构

1 Java的抽象类和接口的区别是什么?

接口和抽象类都是用来定义对象的公共行为的,二者有以下7点不同:
1.定义的关键字不同。
2.子类继承或实现关键字不同。
3.类型扩展不同:抽象类是单继承,而接口是多继承。
4.方法访问控制符:抽象类无限制,只是抽象类中的抽象方法不能被private修饰;而接口有限制,接口默认的是public控制符。
5.属性方法控制符:抽象类无限制,而接口有限制,接口默认的是public控制符。
6.方法实现不同:抽象类中的普通方法必须有实现,抽象方法必须没有实现;而接口中普通方法不能有实现,但在JDK8中的static和defualt方法必须有实现。
7.静态代码块的使用不同:抽象类可以有静态代码块,而接口不能有。

2 谈谈你对Java体系的理解,“Java是解释执行”这句话正确吗?

广义上讲,能够运行于Java虚拟机上的语言及相关程序都属于Java技术体系。Java技术体系包括:Java程序设计语言、各种硬件平台上的Java虚拟机、Class文件格式、JavaAPI类库、来自商业机构和开源社区的第三方Java类库。

JDK是支持Java开发的最小环境,包括Java程序设计语言,Java虚拟机和JavaAPI类库三部分,JRE是支持Java运行的标准环境,包括JavaAPI中JavaSEAPI和Java虚拟机。它首先将源代码编译成二进制字节码(bytecode),然后依赖各种不同平台上的虚拟机来解释执行字节码。从而实现了“一次编译、到处执行”的跨平台特性。

Java语言是解释型的,Java字节码执行有两种方式:
(1)即时编译方式:解释器先将字节码编译成机器码,然后执行该机器码。
(2)解释执行方式:解释器通过每次解释并执行一小段代码来完成java字节码程序所有的操作。

使用哪种方式执行,JVM会自行优化。

3 谈谈类加载过程和双亲委派模型?

Java类加载的全过程,包含加载、验证、准备、解析和初始化这5个阶段。在加载阶段,通过一个类的全限定名来获取此类的二进制字节流,就是依靠类加载器来完成。类加载器的一个作用就是将编译器编译生成的二进制 Class 文件加载到内存中,进而转换成虚拟机中的类。Java系统提供了三种内置的类加载器:

  • 启动类加载器 (Bootstrap Class Loader): 负责加载JDK核心类,通常是 rt.jar 和位于 $JAVA_HOME/jre/lib 下的核心库.
  • 扩展类加载器 (Extensions Class Loader): 负责加载\jre\lib\ext目录下 JAR 包
  • 系统类加载器 (System Class Loader):负责加载所有应用程序级别的类到JVM,它会加载classpath环境变量或 -classpath以及-cp命令行参数中指定的文件

双亲委派模型的定义是:当类加载器收到类的加载请求时,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载请求会传送到顶层的启动类加载器,只有父类加载器无法完成加载请求,才会交由子加载器去加载。

4 谈谈强引用、软引用、弱引用、幻象引用的区别,使用场景是什么?

  • 概念

强引用:new的对象所关联的引用就是强引用,当jvm内存空间不足时,宁肯抛出oom运行时错误,也不会将该引用回收来解决内存不足的问题。除非将其赋值为null或者超过引用的作用范围,该引用指向的对象才会被回收。

软引用:通过SoftReference类来实现,软引用的生命周期比强引用短,只有在jvm抛出OOM之前,才会将该引用指向的对象回收。软引用可以和一个引用队列配合使用,如果软引用指向的对象被垃圾回收器回收,jvm就会把这个软引用加入到引用队列中,后面我们可以调用ReferenceQueue的poll方法来检查是否有我们关心的对象被回收,如果队列为空,则返回一个null,否则返回reference对象。

弱引用:通过WeakReference类来实现,弱引用的生命周期比软引用短,在垃圾回收器扫描它所负责的内存区域的过程中,一旦发现有弱引用的对象,无论当前内存空间是否足够,都会将该引用所指向的对象回收。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快的将其回收,所以我们也可以将弱引用配合引用队列来使用,如果弱引用指向的对象被垃圾回收器回收,jvm就会把这个弱引用加入到引用队列中,后续我们好判断我们所关心的弱引用指向的对象是否被回收。

幻象引用:幻象引用也叫虚引用,通过PhantomReference类来实现。我们无法通过虚引用访问它所指向对象的任何属性和方法,如果一个对象仅仅持有虚引用,那么 它就和没有任何引用一样,随时可能被垃圾回收器回收,因此必须搭配引用队列来使用,当垃圾回收期准备回收一个对象时,如果发现它还有虚引用,那么就在回收之前将它放入引用队列并采取操作。

  • 使用场景

强引用是使用最普遍的引用,一般声明如下:

Object strongReference = new Object();

如果要对强引用进行垃圾回收,需要设置强引用对象为 null ,或者让其超出对象的生命周期范围,则认为改对象不存在引用。

strongReference = null;

软引用可用来实现内存敏感的高速缓存:

// 强引用 
String strongReference = new String("abc"); 
// 软引用 
String str = new String("abc"); 
SoftReference<String> softReference = new SoftReference<String>(str);

软引用垃圾回收代码示例:

if(JVM内存不足) { 
// 将软引用中的对象引用置为null 
softReference = null; 
// 通知垃圾回收器进行回收 
System.gc(); }

弱引用和软引用的区别在于:弱引用拥有更短暂的生命周期,不管内存够不够,都会回收,都会回收它的内存。

Object obj = new Object();
WeakReference<Object> weakReference = new WeakReference<Object>(obj); 
obj = null; System.gc(); 
System.out.println(weakReference.get());
//判断是否有垃圾回收标记,表示即将回收的垃圾
System.out.println(weakReference.isEnqueued());

虚引用代码示例:

Object obj = new Object();
ReferenceQueue refQueue = new ReferenceQueue(); 
PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, refQueue); System.out.println(phantomReference.get()); 
//返回时否从队列中删除
System.out.println(phantomReference.isEnqueued());

对象可达性分析:

  • 强可达(Strongly Reachable),就是当一个对象可以有一个或多个线程可以不通过各种引用访问到的情况。比如,我们新创建一个对象,那么创建它的线程对它就是强可达。
  • 软可达(Softly Reachable),就是当我们只能通过软引用才能访问到对象的状态。
  • 弱可达(Weakly Reachable),类似前面提到的,就是无法通过强引用或者软引用访问,只能通过弱引用访问时的状态。这是十分临近fnalize状态的时机,当弱引用被清除的时候,就符合finalize的条件了。
  • 幻象可达(Phantom Reachable),上面流程图已经很直观了,就是没有强、软、弱引用关联,并且finalize过了,只有幻象引用指向这个对象的时候。当然,还有一个最后的状态,就是不可达(unreachable),意味着对象可以被清除了。

Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

5 有人说“Lambda能让Java慢30倍”,你怎么看?

“Lambda 能让 Java 程序慢 30 倍”这个争论反映了几个方面:

  • 基准测试是一个非常有效的通用手段,让我们以直观、量化的方式,判断程序在特定条件下的性能表现。
  • 基准测试必须明确定义自身的范围和目标,否则很有可能产生误导的结果。前面代码片段本身的逻辑就有瑕疵,更多的开销是源于自动装箱、拆箱(auto-boxing/unboxing),而不是源自 Lambda 和 Stream,所以得出的初始结论是没有说服力的。
  • Lambda/Stream 为 Java 提供了强大的函数式编程能力,但是也需要正视其局限性。一般来说,我们可以认为 Lambda/Stream 提供了与传统方式接近对等的性能,但是如果对于性能非常敏感,就不能完全忽视它在特定场景的性能差异了,例如:初始化的开销。 Lambda 并不算是语法糖,而是一种新的工作机制,在首次调用时,JVM 需要为其构建CallSite实例。这意味着,如果 Java 应用启动过程引入了很多 Lambda 语句,会导致启动过程变慢。其实现特点决定了 JVM 对它的优化可能与传统方式存在差异。增加了程序诊断等方面的复杂性,程序栈要复杂很多,Fluent 风格本身也不算是对于调试非常友好的结构,并且在可检查异常的处理方面也存在着局限性等。

    Java 语言的很多特性,经常有很多似是而非的 “秘籍”,我们有必要去伪存真,以定量、定性的方式探究真相,探讨更加易于推广的实践。找到结论的能力,比结论本身更重要,

6 Java反射机制、动态代理基于什么原理?

反射机制是Java提供的一种基础功能,赋予程序在运行时自省的能力(introspect),通过反射我们可以操作类或者对象,比如获取某个对象的类定义,获取类声明的属性或者方法,调用方法或者操作对象,甚至可以运行时修改类的定义。

动态代理方便运行时动态创建代理对象,动态处理代理方法调用的机制,比如包装rpt,面向切面编程aop等。实现动态代理的方式有很多,比如jdk自身提供的动态代理,就是主要用到了上面的反射机制。还有其他的实现方式,比如字节码操作机制,类似 ASM、cglib、Javassist 等。

7 如何写出安全的Java代码?

这个问题有点宽泛,我们可以用特定类型的安全风险为例如拒绝服务(DoS)攻击,来分析开发者需要关注的点。DoS是一种常见的网络攻击,利用大量机器发送请求,将目标网站的带宽或者其他资源耗尽,导致其无法响应正常用户的请求。

  • 如果使用的是早期的JDK和Applet等技术,攻击者构建合法但恶劣的程序就相对容易,例如,将其线程优先级设置为最高,做一些看起来无害但空耗资源的事情。类似技术已经逐步退出历史舞台,在JDK 9以后,相关模块就已经被移除。
  • 类似加密、解密、图形处理等计算密集型任务,要防范被恶意滥用,以免攻击者通过直接调用或者间接触发方式,消耗系统资源。
  • 利用Java构建类似上传文件或者其他接受输入的服务,需要对消耗系统内存或存储的上限有所控制,因为我们不能将系统安全依赖于用户的合理使用。其中特别注意的是涉及解压缩功能时,就需要防范Zip bomb等特定攻击。
  • Java程序中需要明确释放的资源有很多种,比如文件描述符、数据库连接,甚至是再入锁,任何情况下都应该保证资源释放成功,否则即使平时能够正常运行,也可能被攻击者利用而耗尽某类资源,这也算是可能的DoS攻击来源。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注