Java 核心知识要点解析

JDK、JRE、JVM三者的关系

JDK 包含 JRE

  • JDK 是 Java 开发工具包,它提供了一系列用于开发 Java 程序的工具和资源,是 Java 开发人员进行编程的基础。
  • JRE 是 Java 运行时的环境,它提供了 Java 程序运行所必需的所有组件,包括 Java 虚拟机(JVM)、Java 核心类库以及支持文件等。
  • JDK 包含了 JRE,这意味着当你安装了 JDK,你实际上也安装了 JRE,因为 JRE 是 JDK 的一部分。

JRE 包含 JVM

  • JVM 是 Java 程序的运行核心,它负责将 Java 字节码解释或编译成机器码,在不同的操作系统上提供统一的运行环境,使得 Java 程序能够实现 “一次编写,到处运行” 的特性。
  • JRE 作为 Java 程序的运行环境,其核心就是 JVM,同时还包含了 Java 程序运行所需的 Java 核心类库以及支持文件等。

用一个简单的图像来解释 JDK、JRE、JVM 三者的关系:

JDK8的新特性

语言特性方面

  • Lambda 表达式:一种简洁的表示可传递的匿名函数的方式,使代码更紧凑、易读。例如,使用 Lambda 表达式对一个列表进行遍历并打印每个元素:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
  • 函数式接口:是只包含一个抽象方法的接口,可使用 Lambda 表达式来实例化。如java.util.function包中的PredicateFunctionConsumer等,方便进行函数式编程。
  • 方法引用:提供了一种更简洁的方式来引用已有方法,可与 Lambda 表达式结合使用。如System.out::println可作为一个方法引用传递给forEach方法。

集合框架方面

  • Stream API:用于对集合进行高效的操作,支持过滤、映射、排序、归约等操作。例如,从一个整数列表中筛选出偶数并求和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); 
int sum = numbers.stream().filter(n -> n % 2 == 0).mapToInt(Integer::intValue).sum();
  • Optional 类:用于解决空指针异常问题,它是一个可能包含或不包含非空值的容器类。通过Optional.ofNullable()等方法创建Optional对象,然后使用ifPresent()等方法进行操作。

新的日期和时间 API

  • 引入了java.time包,提供了更强大、更易用的日期和时间处理类,如LocalDateLocalTimeLocalDateTimeDateTimeFormatter等,用于处理日期、时间、时区等。例如,获取当前日期并格式化为指定字符串:
LocalDate now = LocalDate.now(); String formattedDate = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

其他方面

  • 接口默认方法和静态方法:接口中可以定义默认方法和静态方法,这使得接口可以在不破坏实现类的情况下添加新的方法。

  • 类型注解:可在代码中对变量、方法参数等添加类型注解,用于提供更多的类型信息,方便进行代码分析和工具处理。

Stream流常见的作用

数据筛选与过滤

  • 基本过滤:通过filter方法可以方便地对集合中的元素进行筛选,只保留满足特定条件的元素。例如,从一个整数列表中筛选出所有偶数,可以使用list.stream().filter(num -> num % 2 == 0).collect(Collectors.toList());
  • 复杂条件筛选:可以使用多个filter方法串联,或者在filter方法中使用复杂的逻辑表达式,实现更复杂的筛选条件。比如,从一个员工列表中筛选出年龄在 30 到 40 岁之间且工资高于 5000 的员工。

数据转换与映射

  • 类型转换:使用map方法可以将集合中的元素从一种类型转换为另一种类型。例如,将一个字符串列表中的所有字符串转换为大写形式,可以使用list.stream().map(String::toUpperCase).collect(Collectors.toList());
  • 对象属性提取:对于包含对象的集合,可以使用map方法提取对象的某个属性,形成一个新的集合。比如,从一个包含学生对象的集合中提取出所有学生的姓名,组成一个字符串列表。

数据排序

  • 自然排序:使用sorted方法可以对集合中的元素进行自然排序。对于实现了Comparable接口的元素类型,sorted方法会按照默认的比较规则进行排序。例如,对一个整数列表进行排序,可以使用list.stream().sorted().collect(Collectors.toList());
  • 定制排序:通过传入自定义的Comparator接口实现,可以按照特定的规则进行排序。比如,对一个员工列表按照工资从高到低进行排序,可以使用list.stream().sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary())).collect(Collectors.toList());

数据聚合与统计

  • 基本聚合操作:使用Collectors类提供的各种方法,可以对集合中的元素进行聚合操作,如求和、求平均值、求最大值、求最小值等。例如,计算一个整数列表的总和,可以使用list.stream().collect(Collectors.sumInt(Integer::intValue));
  • 分组统计:通过groupingBy方法可以按照指定的属性对集合中的元素进行分组,并对每组元素进行统计。比如,对一个员工列表按照部门进行分组,然后统计每个部门的员工人数。

数据收集与转换为其他数据结构

  • 转换为列表、集合等:使用collect方法结合Collectors类的相应方法,可以将流中的元素收集到不同的集合类型中,如ListSetMap等。例如,将一个流中的元素收集到List中,可以使用list.stream().collect(Collectors.toList());
  • 转换为数组:使用toArray方法可以将流中的元素转换为数组。例如,将一个整数流转换为整数数组,可以使用int[] array = list.stream().mapToInt(Integer::intValue).toArray();

并行处理

  • 提高处理效率:Stream 流支持并行处理,通过调用parallel方法,可以将流中的数据分成多个子任务,在多个处理器核心上并行执行,从而提高数据处理的效率。

JVM内存结构,堆栈的区别

JVM内存结构

程序计数器
  • 定义:是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
  • 作用:在多线程环境下,用于记录当前线程执行的位置,以便在线程切换后能恢复到正确的执行位置。
  • 特点:线程私有,其内存空间的生命周期与线程相同,并且不会出现内存溢出的情况。
Java 虚拟机栈
  • 定义:也是线程私有的,它的生命周期与线程相同。
  • 作用:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当一个方法被调用时,会在栈中创建一个栈帧,用于存储该方法的相关信息,方法执行完毕后,栈帧出栈。
  • 特点:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果栈可以动态扩展,当扩展时无法申请到足够的内存,则会抛出 OutOfMemoryError 异常。
本地方法栈
  • 定义:与 Java 虚拟机栈类似,不过它是为本地方法服务的,本地方法是由其他语言(如 C、C++)编写的方法。
  • 作用:用于存储本地方法执行时的相关信息,如本地方法的参数、返回值、局部变量等。
  • 特点:也会出现 StackOverflowError 和 OutOfMemoryError 异常。
  • 定义:是 JVM 中最大的一块内存区域,被所有线程共享。
  • 作用:主要用于存储对象实例和数组,是 Java 程序运行时动态分配内存的主要区域。
  • 特点:垃圾回收器主要管理的区域,当堆中没有足够的内存空间来分配新的对象时,会抛出 OutOfMemoryError 异常。
方法区
  • 定义:也是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
  • 作用:在类加载时,将类的相关信息存储到方法区,供程序运行时使用。
  • 特点:方法区的大小可以是固定的,也可以是动态扩展的,如果方法区无法满足内存分配需求,会抛出 OutOfMemoryError 异常。
运行时常量池
  • 定义:是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。

  • 作用:在类加载后,将常量池中的符号引用转换为直接引用,供程序运行时使用。

  • 特点:运行时常量池具有动态性,在运行期间可以将新的常量放入池中。

堆栈的区别

内存分配方式
  • :是在 JVM 启动时创建的一块内存区域,其大小可以通过参数进行设置,在运行期间可以动态地分配和回收内存。对象实例和数组在创建时会在堆中分配内存,通过new关键字创建的对象都会存储在堆中。
  • :是线程私有的内存区域,每个线程在创建时都会分配一个独立的栈空间。栈的内存分配是在方法调用时进行的,当一个方法被调用时,会在栈中为该方法创建一个栈帧,方法执行完毕后,栈帧会被销毁,内存自动回收。
存储内容
  • :主要存储对象实例和数组,包括对象的成员变量、对象本身等。这些对象可以被多个线程共享,只要有引用指向它们,它们就会一直存在于堆中,直到被垃圾回收器回收。
  • :主要存储局部变量表、操作数栈、动态链接、方法出口等与方法执行相关的信息。局部变量表中存储的是方法中的局部变量,包括基本数据类型和对象引用,这些变量在方法执行完毕后就会被销毁。
内存管理方式
  • :由垃圾回收器进行管理,垃圾回收器会自动检测堆中不再被引用的对象,并回收它们占用的内存空间。堆的内存管理相对复杂,因为需要处理对象之间的引用关系,以及不同代际对象的回收策略等。
  • :内存的分配和回收是由系统自动完成的,不需要程序员手动干预。当方法调用结束时,栈帧会自动出栈,栈帧中占用的内存空间也会自动释放,因此栈的内存管理相对简单。
内存空间大小
  • :通常是 JVM 中最大的一块内存区域,其大小可以通过-Xmx-Xms等参数进行设置。堆的大小需要根据应用程序的实际需求进行调整,如果堆设置得太小,可能会导致频繁的垃圾回收,影响程序的性能;如果堆设置得太大,可能会导致内存浪费。
  • :每个线程的栈空间大小通常是固定的,默认大小一般在几百 KB 到 1MB 左右,可以通过-Xss参数进行调整。栈空间大小的设置需要根据线程的执行情况进行调整,如果栈空间设置得太小,可能会导致栈溢出异常;如果栈空间设置得太大,可能会导致内存浪费。

Java会存在内存泄漏吗?

内存泄漏指的是程序在运行过程中,不断申请内存空间却没有及时释放不再使用的内存,导致可用内存逐渐减少,最终可能影响程序的正常运行甚至导致系统崩溃。

当一些不再被使用的对象仍然被其他对象引用,导致垃圾回收器无法回收这些对象占用的内存时,就会发生内存泄漏。常见的情况包括:

  • 对象持有外部资源未释放:如打开文件或数据库连接后未关闭,这些资源会一直占用内存,直到程序结束。
  • 集合类中对象引用未清理:在使用集合类时,不断向其中添加对象,但在使用完毕后没有及时将对象从集合中移除,导致集合中的对象无法被回收。
  • 内部持有外部对象引用:在一些内部类或匿名类中,可能会持有外部类的对象引用,如果外部类对象不再使用,但内部类对象仍然被其他地方引用,就会导致外部类对象无法被回收。

有什么办法可以把方法区内存撑爆

大量动态生成类

  • 使用字节码操作库:通过 Java 字节码操作库如 Byte Buddy、ASM 等,在程序运行过程中动态生成大量的类。这些类会被加载到方法区,当生成的类数量足够多时,就可能导致方法区内存溢出。
  • 使用动态代理:在循环中不断创建动态代理类,每个动态代理类都会在方法区中占用一定的空间,大量创建动态代理类可能导致方法区内存溢出。

大量加载类库

  • 加载大量第三方库:在程序中通过反射等方式动态加载大量的第三方库,这些库中的类会被加载到方法区。如果加载的库数量足够多且库中的类比较复杂,就可能导致方法区内存溢出。

生成大量常量和静态变量

  • 在循环中定义大量静态变量:在程序中通过循环不断定义大量的静态变量,这些静态变量会存储在方法区中。当静态变量的数量足够多时,就可能导致方法区内存溢出。

常见的垃圾回收机制

引用计数法

  • 原理:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器值就加 1;当引用失效时,计数器值就减 1。当对象的引用计数器值为 0 时,就表示该对象不再被使用,可以被回收。
  • 优点:实现简单,回收效率高,一旦对象没有引用,就会立即被回收。
  • 缺点:无法解决循环引用的问题。例如,对象 A 引用对象 B,对象 B 又引用对象 A,此时它们的引用计数都不为 0,但实际上这两个对象可能已经不再被其他外部对象引用,应该被回收。

标记 - 清除算法

  • 原理:该算法分为 “标记” 和 “清除” 两个阶段。首先从根对象(如栈中的变量、静态变量等)开始,对所有可达的对象进行标记;然后遍历整个堆内存,清除未被标记的对象。
  • 优点:可以解决循环引用的问题,因为只要对象不可达,就会被清除。
  • 缺点:标记和清除的过程效率较低,而且清除后会产生大量不连续的内存碎片,可能导致后续分配大对象时无法找到足够的连续内存空间,从而触发更频繁的垃圾回收。

复制算法

  • 原理:将内存空间划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块内存中,然后把使用过的这块内存空间全部清除掉。
  • 优点:实现简单,运行高效,不会产生内存碎片。
  • 缺点:可用内存空间缩小为原来的一半,浪费了一定的内存空间。如果存活对象较多,复制的成本也会较高。

标记 - 压缩算法

  • 原理:也叫标记 - 整理算法,同样先进行标记阶段,标记出所有存活的对象。然后将所有存活的对象向一端移动,使它们紧凑排列,最后直接清理掉边界以外的内存空间。
  • 优点:解决了标记 - 清除算法的内存碎片问题,同时也避免了复制算法中内存空间浪费的问题。
  • 缺点:移动对象的成本较高,尤其是在存活对象较多的情况下,可能会影响垃圾回收的效率。

分代收集算法

  • 原理:根据对象的存活周期将内存划分为不同的代,一般分为新生代和老年代。新生代中对象的存活率较低,通常采用复制算法进行垃圾回收;老年代中对象的存活率较高,一般采用标记 - 清除或标记 - 压缩算法进行垃圾回收。
  • 优点:针对不同代的特点采用不同的垃圾回收算法,提高了垃圾回收的效率。
  • 缺点:实现较为复杂,需要在不同代之间进行协调和切换。

JVM 强弱引用的区别

  • 强引用:是最常见的引用方式,如通过new关键字创建的对象引用。只要强引用存在,垃圾回收器就不会回收被引用的对象。
  • 弱引用:弱引用的对象在垃圾回收时,如果该对象只被弱引用关联,那么无论内存是否充足,都会被回收。弱引用通常用于实现一些缓存机制,当内存不足时,可以及时释放缓存对象。

怎么判断一个对象是否达到回收标准

  • 引用计数法:给对象添加一个引用计数器,当有一个地方引用该对象时,计数器加 1;当引用失效时,计数器减 1。当计数器为 0 时,对象就可以被回收。但这种方法无法解决循环引用的问题。
  • 可达性分析算法:从一系列称为 “GC Roots” 的对象作为起始点,向下搜索遍历对象图,当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可达的,会被判定为可回收对象。

双亲委派和类加载机制

  • 双亲委派模型:当一个类加载器收到类加载的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
  • 类加载机制:主要包括加载、验证、准备、解析、初始化五个阶段。加载阶段将类的字节码文件加载到内存中;验证阶段对字节码文件的格式、语义等进行验证;准备阶段为类的静态变量分配内存并设置默认值;解析阶段将符号引用转换为直接引用;初始化阶段执行类的初始化代码,如静态变量赋值、静态代码块执行等。

一个class类的运行流程

编写与编译

  • 编写源文件:开发人员使用 Java 编程语言编写以.java为扩展名的源文件,其中包含一个或多个类的定义。在类定义中,可以包含成员变量、方法、构造函数等。
  • 编译成字节码:使用 Java 编译器(javac)将源文件编译成字节码文件,字节码文件以.class为扩展名。编译过程中,编译器会对源文件进行语法检查、语义分析等操作,将 Java 代码转换为虚拟机能够识别的字节码指令。

类加载

  • 加载阶段:类加载器负责将字节码文件加载到 Java 虚拟机中。根据双亲委派模型,首先由引导类加载器尝试加载,如果找不到则由扩展类加载器加载,再找不到则由应用程序类加载器加载。类加载器会将字节码文件读取到内存中,并生成一个代表该类的Class对象。
  • 验证阶段:对加载的字节码进行验证,确保其符合 Java 虚拟机规范,包括文件格式验证、元数据验证、字节码验证和符号引用验证等。
  • 准备阶段:为类的静态变量分配内存空间,并设置默认初始值。例如,对于int类型的静态变量,初始值为0;对于Object类型的静态变量,初始值为null
  • 解析阶段:将类中的符号引用转换为直接引用。符号引用是一种在编译时使用的、对目标对象的描述性引用,而直接引用是在运行时能够直接定位到目标对象的指针或偏移量。

类的初始化

  • 执行静态初始化块:如果类中定义了静态初始化块,那么在类初始化阶段会按照顺序执行这些静态初始化块中的代码。静态初始化块可以用于对静态变量进行复杂的初始化操作,或者执行一些在类加载时需要执行的一次性任务。
  • 初始化静态变量:对类中的静态变量进行显式赋值操作,将其初始化为指定的值。

对象创建与使用

  • 创建对象:使用new关键字创建类的实例对象。在创建对象时,首先会在堆内存中为对象分配内存空间,然后对对象的成员变量进行默认初始化,接着调用构造函数对对象进行初始化。
  • 对象使用:通过对象引用调用对象的方法或访问对象的成员变量,执行相应的业务逻辑。
  • 对象销毁:当对象不再被使用时,垃圾回收器会自动回收对象占用的内存空间。垃圾回收器会根据对象的引用情况判断对象是否可达,如果对象不可达,则将其标记为可回收对象,并在适当的时候进行回收。

类的卸载

  • 满足卸载条件:当一个类在虚拟机中不再被使用,并且满足一定的卸载条件时,虚拟机可能会对其进行卸载。一般来说,当类的Class对象及其所有实例对象都不再被引用,并且加载该类的类加载器也不再被引用时,该类就有可能被卸载。

  • 执行卸载操作:虚拟机执行类的卸载操作,释放该类占用的内存空间,包括类的字节码、静态变量、方法等占用的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值