【Java】基础(三) | JDK
JDK 和 JRE 有什么区别?
JRE(Java Runtime Environment)指的是 Java 运行环境,包含了 JVM、核心类库和其他支持运行 Java 程序的文件。
- JVM(Java Virtual Machine):执行 Java 字节码,提供了 Java 程序的运行环境。
- 核心类库:一组标准的类库(如 java.lang、java.util 等),供 Java 程序使用。
- 其他文件:如配置文件、库文件等,支持 JVM 的运行。
JDK(Java Development Kit)可以视为 JRE 的超集,是用于开发 Java 程序的完整开发环境,它包含了 JRE,以及用于开发、调试和监控 Java 应用程序的工具。
- JRE:JDK 包含了完整的 JRE,因此它也能运行 Java 程序。
- 开发工具:如编译器(javac)、调试器(jdb)、打包工具(jar)等,用于开发和管理 Java 程序。
- 附加库和文件:支持开发、文档生成和其他开发相关的任务。
列举一下 JDK 提供的主要工具:
- javac:Java 编译器,用于将 Java 源代码(.java 文件)编译成字节码(.class 文件)。
- java:Java 应用程序启动器,用于运行 Java 应用程序。
- javadoc:文档生成器,用于从 Java 源代码中提取注释并生成 HTML 格式的 API 文档。
- jar:归档工具,用于创建和管理 JAR(Java ARchive)文件。
- jdb:Java 调试器,用于调试 Java 程序。
- jps:Java 进程状态工具,用于列出当前所有的 Java 进程。
- jstat:JVM 统计监视工具,用于监视 JVM 统计信息。
- jstatd:JVM 统计监视守护进程,用于在远程监视 JVM 统计信息。
- jmap:内存映射工具,用于生成堆转储(heap dump)、查看内存使用情况。
- jhat:堆分析工具,用于分析堆转储文件。
- jstack:线程栈追踪工具,用于打印 Java 线程的栈追踪信息。
- javap:类文件反汇编器,用于反汇编和查看 Java 类文件。
- jdeps:Java 类依赖分析工具,用于分析类文件或 JAR 文件的依赖关系。
你使用过哪些 JDK 提供的工具?
这个题目主要考察你平日里面是否有过利用 JDK 的工具进行问题的分析、排查。
(注意,这里不要说什么 javac 之类的命令,主要想考察的是问题分析、排查方面的内容)
比如排查内存问题的时候,利用 jmap 生成堆转储文件,下载后利用 Eclipse 的 MAT 工具进行分析。
如果大家没有排查经验,强烈建议去尝试一下,难度不高的。
我列几个常见工具,建议可以用用,还是很简单的。
- jps:虚拟机进程状况工具
- jstat:虚拟机统计信息监视工具
- jmap:Java内存映像工具
- jhat:虚拟机堆转储快照分析工具
- jstack:Java堆栈跟踪工具
- jinfo:Java配置信息工具
- VisualVM:图形化工具,可以得到虚拟机运行时的一些信息:内存分析、CPU 分析等等,在 jdk9 开始不再默认打包进 jdk 中。
工具其实还有很多,上个问题也有列举。
虽然面试这样答可能就差不多了,但还是希望大家可以自己找机会用用,没机会就自己给自己创造机会。
因为这属于线上排查能力,只有真正实践了,到时候自己负责的项目真的出了问题,才不至于手忙脚乱。我们毕竟是工程师,问题解决能力必须掌握。
JDK8 有哪些新特性?
JDK8 较为重要和平日里经常被问的特性如下:
- 用元空间替代了永久代
- 引入了 Lambda 表达式
- 引入了日期类、接口默认方法、静态方法
- 新增 Stream 流式接口
- 引入 Optional 类
- 新增了 CompletableFuture 、StampedLock 等并发实现类。
如果你对 HashMap、ConcurrentHashMap 面试题有准备的话,这时候也可以抛出来,引导面试官来询问。比如:Java 8 修改了 HashMap 和 ConcurrentHashMap 的实现。
元空间替代了永久代(JVM相关)
因为 JDK8 要把 JRockit 虚拟机和 Hotspot 虚拟机融合,而 JRockit 没有永久代,所以把 Hotspot 永久代给去了(本质也是永久代回收效率太低)。
其实永久代之前的存在就有点尴尬,归堆管但是实际上回收效率很低,你听听这名字永久代,不就是对象几乎是永久存在吗?
而且有永久代的话,如果永久代满了也会触发 full gc,触发了回收但是回收率又很低,所以很不划算。
因此官方借着和 JRockit 合并就把永久代也干掉,用元空间代替。元空间放在堆外,至少没堆内内存的限制了。
Lambda 表达式
Lambda 是 Java 8 引入的一种匿名函数,可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。
其本质是作为函数式接口的实例。例如:
// 传统方式 |
日期类
Java 8 引入了新的日期和时间 API(位于 java.time 包中),它们更加简洁和易于使用,解决了旧版日期时间 API 的许多问题。
例如 Date
、Calendar
都是可变类且线程不安全。而新的日期类都是不可变的,一旦创建就不能修改,这样可以避免意外的修改,提升代码的安全性和可维护性。
LocalDate date = LocalDate.now(); |
Date
本身不包含时区信息,必须使用 Calendar
类来处理时区,但使用起来非常复杂且容易出错。
新 API 提供了专门的时区类(如 ZonedDateTime
, OffsetDateTime
, ZoneId
等),简化了时区处理,并且这些类的方法更加直观和易用。
接口默认方法、静态
默认方法允许在接口中定义方法的默认实现,这样接口的实现类不需要再实现这些方法。之所以提供静态方法,是为了将相关的方法内聚在接口中,而不必创建新的对象。
Stream 流式接口
Stream API 提供了一种高效且易于使用的方式来处理数据集合。它支持链式操作、惰性求值和并行处理。
List<String> list = Arrays.asList("a", "b", "c", "d"); |
Optional
Optional
类用来解决可能出现的 NullPointerException
问题,提供了一种优雅的方式来处理可能为空的值。
Optional<String> optional = Optional.of("mianshiya.com"); |
CompletableFuture
CompletableFuture 提供了一个新的异步编程模型,简化了异步任务的编写和管理。
CompletableFuture.supplyAsync(() -> "Hello") |
什么是 Java 中的动态代理?
动态代理是 Java 提供的一种强大机制,用于在运行时创建代理类或代理对象,以实现接口的行为,而不需要提前在代码中定义具体的类。动态是相对于静态来说的,之所以动态就是因为动作发生在运行时。
代理可以看作是调用目标的一个包装,通常用来在调用真实的目标之前进行一些逻辑处理,消除一些重复的代码。
静态代理指的是我们预先编码好一个代理类,而动态代理指的是运行时生成代理类。
动态更加方便,可以指定一系列目标来动态生成代理类(AOP),而不像静态代理需要为每个目标类写对应的代理类。
代理也是一种解耦,目标类和调用者之间的解耦,因为多了代理类这一层。
动态代理的主要用途包括:
- 简化代码:通过代理模式,可以减少重复代码,尤其是在横切关注点(如日志记录、事务管理、权限控制等)方面。
- 增强灵活性:动态代理使得代码更具灵活性和可扩展性,因为代理对象是在运行时生成的,可以动态地改变行为。
- 实现 AOP:动态代理是实现面向切面编程(AOP, Aspect-Oriented Programming)的基础,可以在方法调用前后插入额外的逻辑。
JDK 动态代理和 CGLIB 动态代理有什么区别?
JDK 动态代理是基于接口的,所以要求代理类一定是有定义接口的。
CGLIB 基于 ASM 字节码生成工具,它是通过继承的方式来实现代理类,所以要注意 final 方法。
它们之间的性能随着 JDK 版本的不同而不同,以下内容取自:haiq的博客
- jdk6 下,在运行次数较少的情况下,jdk动态代理与 cglib 差距不明显,甚至更快一些;而当调用次数增加之后, cglib 表现稍微更快一些
- jdk7 下,情况发生了逆转!在运行次数较少(1,000,000)的情况下,jdk动态代理比 cglib 快了差不多30%;而当调用次数增加之后(50,000,000), 动态代理比 cglib 快了接近1倍
- jdk8 表现和 jdk7 基本一致
扩展 JDK 动态代理
JDK 动态代理是基于接口的代理,因此要求代理类一定是有定义的接口,使用 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口实现。
// 接口 |
JDK 动态代理处理类:
import java.lang.reflect.InvocationHandler; |
创建并使用动态代理对象:
import java.lang.reflect.Proxy; |
Java 运行时异常和编译时异常之间的区别是什么?
在 Java 中其实分了两大类异常,受检异常(checked exception)和非受检异常(unchecked exception),它们之间的差别主要在于是否是编译时检查。
受检异常(checked exception)其实就是编译时异常,继承自 Exception,即在编译阶段检查代码中可能会出现的异常,需要开发者显式的捕获(catch)或声明抛出(throw)这种异常,否则编译就会报错,这是一种强制性规范。
常见的有:IOException、SQLException、FileNotFoundException 等等。
非受检异常(unchecked exception)就是运行时异常,继承自 RuntimeException 类。是指在运行期间可能会抛出的异常,编译期不强制要求处理,之所以不强制是因为它可以通过完善代码避免报错。
常见的有:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException 等等。