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 较为重要和平日里经常被问的特性如下:

  1. 用元空间替代了永久代
  2. 引入了 Lambda 表达式
  3. 引入了日期类、接口默认方法、静态方法
  4. 新增 Stream 流式接口
  5. 引入 Optional 类
  6. 新增了 CompletableFuture 、StampedLock 等并发实现类。

如果你对 HashMap、ConcurrentHashMap 面试题有准备的话,这时候也可以抛出来,引导面试官来询问。比如:Java 8 修改了 HashMap 和 ConcurrentHashMap 的实现。

元空间替代了永久代(JVM相关)

因为 JDK8 要把 JRockit 虚拟机和 Hotspot 虚拟机融合,而 JRockit 没有永久代,所以把 Hotspot 永久代给去了(本质也是永久代回收效率太低)。

其实永久代之前的存在就有点尴尬,归堆管但是实际上回收效率很低,你听听这名字永久代,不就是对象几乎是永久存在吗?

而且有永久代的话,如果永久代满了也会触发 full gc,触发了回收但是回收率又很低,所以很不划算。

因此官方借着和 JRockit 合并就把永久代也干掉,用元空间代替。元空间放在堆外,至少没堆内内存的限制了。

Lambda 表达式

Lambda 是 Java 8 引入的一种匿名函数,可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。

其本质是作为函数式接口的实例。例如:

// 传统方式
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("mianshiya.com");
}
};

// Lambda 表达式
Runnable runnable2 = () -> System.out.println("mianshiya.com");

日期类

Java 8 引入了新的日期和时间 API(位于 java.time 包中),它们更加简洁和易于使用,解决了旧版日期时间 API 的许多问题。

例如 DateCalendar 都是可变类且线程不安全。而新的日期类都是不可变的,一旦创建就不能修改,这样可以避免意外的修改,提升代码的安全性和可维护性。

LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();

Date 本身不包含时区信息,必须使用 Calendar 类来处理时区,但使用起来非常复杂且容易出错。

新 API 提供了专门的时区类(如 ZonedDateTime, OffsetDateTime, ZoneId 等),简化了时区处理,并且这些类的方法更加直观和易用。

接口默认方法、静态

默认方法允许在接口中定义方法的默认实现,这样接口的实现类不需要再实现这些方法。之所以提供静态方法,是为了将相关的方法内聚在接口中,而不必创建新的对象。

Stream 流式接口

Stream API 提供了一种高效且易于使用的方式来处理数据集合。它支持链式操作、惰性求值和并行处理。

List<String> list = Arrays.asList("a", "b", "c", "d");
List<String> result = list.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());

Optional

Optional 类用来解决可能出现的 NullPointerException 问题,提供了一种优雅的方式来处理可能为空的值。

Optional<String> optional = Optional.of("mianshiya.com");
optional.ifPresent(System.out::println);

CompletableFuture

CompletableFuture 提供了一个新的异步编程模型,简化了异步任务的编写和管理。

CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println);

什么是 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 接口实现。

// 接口
public interface Service {
void perform();
}

// 需要被代理的实现类
public class ServiceImpl implements Service {
@Override
public void perform() {
System.out.println("test");
}
}

JDK 动态代理处理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ServiceInvocationHandler implements InvocationHandler {
private final Object target;

public ServiceInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method invoke");
Object result = method.invoke(target, args);
System.out.println("After method invoke");
return result;
}
}

创建并使用动态代理对象:

import java.lang.reflect.Proxy;

public class DynamicProxyDemo {
public static void main(String[] args) {
Service target = new ServiceImpl();
Service proxy = (Service) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new ServiceInvocationHandler(target)
);

proxy.perform();
}
}

Java 运行时异常和编译时异常之间的区别是什么?

在 Java 中其实分了两大类异常,受检异常(checked exception)和非受检异常(unchecked exception),它们之间的差别主要在于是否是编译时检查

受检异常(checked exception)其实就是编译时异常,继承自 Exception,即在编译阶段检查代码中可能会出现的异常,需要开发者显式的捕获(catch)或声明抛出(throw)这种异常,否则编译就会报错,这是一种强制性规范。

常见的有:IOException、SQLException、FileNotFoundException 等等。

非受检异常(unchecked exception)就是运行时异常,继承自 RuntimeException 类。是指在运行期间可能会抛出的异常,编译期不强制要求处理,之所以不强制是因为它可以通过完善代码避免报错。

常见的有:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException 等等。