1、JDK、JRE、JVM三者的关系
- JDK:Java开发工具包,包括编译工具(javac.exe)、打包工具(jar.exe)等,也包含JRE。JDK是开发Java程序的主要工具包,包括了Java运行环境、Java工具和Java基础的类库(Java API)。
- JRE:Java运行环境,包括JVM标准实现(JVM)和Java核心类库。JRE是运行Java程序必不可少的运行时环境。
- JVM:Java虚拟机,负责将Java字节码转换成具体系统平台的机器指令,执行Java程序。
JDK、JRE和JVM之间的关系
- JDK包含JRE:JDK包含了JRE以及Java开发工具(如javac、java等)。
- JRE包含JVM:JRE包含了JVM以及Java核心类库。
- 关系总结:JDK = JRE + Java开发工具;JRE = JVM + Java核心类库。
JDK、JRE和JVM的区别
- 功能不同:JDK用于Java程序的开发,JRE用于运行编译好的class文件。
- 包含内容不同:JDK包含JRE和开发工具,JRE包含JVM和核心类库。
- 使用场景不同:开发人员需要JDK,而普通用户只需要JRE来运行Java程序。
2、JDK8的新特性
- Lambda表达式
lambda
表达式 (也称为闭包), 本质上是一段匿名内部类或一段可以传递的代码 . 可以理解为使用更加方便的"语法糖",但原理不变的编写语法。 它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .
- 函数式接口
有且仅有一个抽象方法的接口
- 方法引用和构造器调用
- Stream API
Stream API提供了对集合对象的一系列顺序操作,如筛选、排序等,使得对集合的处理更加方便和函数式
- 接口中的默认方法和静态方法
JDK8允许接口中有默认方法和静态方法,这增加了接口的灵活性,使得接口不仅可以定义方法声明,还可以提供默认实现
- 新的日期时间API
JDK8引入了新的日期和时间API,解决了旧版日期时间API的许多问题,新的日期类都是不可变的,提高了代码的安全性和可维护性
- Optional
JDK8引入了新的日期和时间API,解决了旧版日期时间API的许多问题,新的日期类都是不可变的,提高了代码的安全性和可维护性
3、JVM内存结构,堆栈的区别
- JVM内存划分:方法区、堆、栈、程序计数器、本地方法栈
- 各内存区域的作用
- 方法区:存储类信息、常量、静态变量等
- 堆:存储对象实例和数组
- 栈:方法调用的执行上下文
- 程序计数器:记录当前线程执行字节码的位置
- 本地方法栈:执行native方法
3、JVM内存结构,堆栈的区别
堆(Heap):
堆的定义:JVM管理的最大运行时数据区域
堆的特点
1、所有对象实例和数组都在堆上分配
2、由垃圾收集器管理内存
3、可以是固定大小,也可以是可扩展
堆的分代
1、新生代和老年代
各代的特点和作用:新生代:存放新创建的对象,老年代:经过多次GC后仍存活的对象
栈(Stack):
- 栈的定义:线程私有的内存区域
- 栈的特点
- 存放局部变量、方法参数、返回地址等
- 遵循先进后出(LIFO)的原则
- 随着方法的调用而创建,随着方法的返回而销毁
- 栈帧的结构
- 局部变量表
- 操作数栈
- 动态链接
- 方法返回地址
- 方法调用过程
- 压栈和出栈
- 栈帧的创建和销毁
堆和栈的区别:
存储内容
堆:对象实例和数组
栈:局部变量、方法参数、返回地址等
存储方式
堆:动态分配,由垃圾收集器管理
栈:静态分配,由编译器自动管理
生命周期
堆:对象的创建和销毁由GC决定
栈:随着方法的进入和退出而分配和释放
大小限制
堆:受限于机器的物理内存
栈:较小,一般为几百KB到几兆字节
内存管理
堆:由垃圾收集器管理,存在碎片化问题
栈:由编译器自动管理,不会存在碎片化
线程共享
堆:所有线程共享
栈:每个线程私有
堆和栈的交互
- 引用传递
- 基本类型:值传递
- 引用类型:引用传递
- 方法调用
- 压栈和出栈
- 局部变量表和操作数栈
- 对象创建
- 在堆中分配内存
- 在栈中存放对象引用
4、双亲委派和类加载机制
类加载:Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
类加载过程:
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析三个部分统称为连接。加载、检验、准备、初始化和卸载这个五个阶段的顺序是固定的,而解析则未必。为了支持动态绑定,解析这个过程可以发生在初始化阶段之后。
4.1、 加载
加载过程主要完成三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个类字节流代表的静态存储结构转为方法区的运行时数据结构。
- 在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。
这个过程主要就是类加载器完成。
4.2、校验(验证)
此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。
- 文件格式验证:基于字节流验证。
- 元数据验证:基于方法区的存储结构验证。
- 字节码验证:基于方法区的存储结构验证。
- 符号引用验证:基于方法区的存储结构验证。
4.3、 准备
为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间。
4.4、解析
类型中的符号引用转换为直接引用。
符号引用 与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用 可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在主要有以下四种:
类或接口的解析
字段解析
类方法解析
接口方法解析
4.5、 初始化
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。
java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻"初始化"(加载,验证,准备,自然需要在此之前开始):
使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
虚拟机启动时,用户会先初始化要执行的主类(含有main)
jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。
4、6 类加载器
把类加载阶段的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作交给虚拟机之外的类加载器来完成。这样的好处在于,我们可以自行实现类加载器来加载其他格式的类,只要是二进制字节流就行,这就大大增强了加载器灵活性。系统自带的类加载器分为三种:
启动类加载器。
扩展类加载器。
应用程序类加载器
4.7、双亲委派机制
双亲委派机制工作过程:
如果一个类加载器收到了类加载器的请求.它首先不会自己去尝试加载这个类.而是把这个请求委派给父加载器去完成.每个层次的类加载器都是如此.因此所有的加载请求最终都会传送到Bootstrap类加载器(启动类加载器)中.只有父类加载反馈自己无法加载这个请求(它的搜索范围中没有找到所需的类)时.子加载器才会尝试自己去加载。
双亲委派模型的优点:java类随着它的加载器一起具备了一种带有优先级的层次关系.
例如类java.lang.Object,它存放在rt.jart之中.无论哪一个类加载器都要加载这个类.最终都是双亲委派模型最顶端的Bootstrap类加载器去加载.因此Object类在程序的各种类加载器环境中都是同一个类.相反.如果没有使用双亲委派模型.由各个类加载器自行去加载的话.如果用户编写了一个称为"java.lang.Object"的类.并存放在程序的ClassPath中.那系统中将会出现多个不同的Object类.java类型体系中最基础的行为也就无法保证.应用程序也将会一片混乱.
5、常见的垃圾回收机制
一、标记 - 清除算法(Mark and Sweep)
-
工作流程
- 标记阶段:从根对象(如栈中的引用、静态变量等)开始,遍历所有可达对象,并标记它们为 "存活" 状态。
- 清除阶段:扫描整个堆内存,回收未被标记的对象所占用的空间。
-
优缺点
- 优点:实现简单,是基础的垃圾回收算法。
- 缺点:
- 会产生内存碎片,可能导致后续分配大对象时无法找到连续的内存空间。
- 回收过程中需要暂停整个应用程序,即 "Stop The World"(STW),可能会导致较长时间的停顿。
二、复制算法(Copying)
-
工作流程
- 将内存分为两块相等的区域,一般称为 From 区和 To 区。
- 当进行垃圾回收时,将 From 区中存活的对象复制到 To 区,然后清空 From 区。
- 下次垃圾回收时,角色互换,From 区变为 To 区,To 区变为 From 区。
-
优缺点
- 优点:
- 不会产生内存碎片,因为每次回收都是将存活对象复制到新的区域,保证了内存的连续性。
- 回收过程相对高效,只需要处理存活对象。
- 缺点:
- 内存利用率只有 50%,因为总有一块区域是空闲的,用于复制存活对象。
- 优点:
三、标记 - 整理算法(Mark and Compact)
-
工作流程
- 标记阶段与标记 - 清除算法相同,标记出所有存活对象。
- 整理阶段:将所有存活对象向一端移动,然后清理掉端边界以外的内存空间。
-
优缺点
- 优点:
- 不会产生内存碎片,解决了标记 - 清除算法的缺点。
- 相比复制算法,内存利用率更高。
- 缺点:
- 整理过程需要移动对象,开销相对较大。
- 优点:
四、分代回收算法(Generational Collection)
-
基本思想
- 根据对象的生命周期将内存分为不同的代,一般分为年轻代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation,在 Java 8 及之后被元空间取代)。
- 不同代的对象具有不同的特点,采用不同的垃圾回收算法。
-
年轻代回收
- 主要采用复制算法,因为年轻代中的对象大多生命周期较短,复制存活对象的成本相对较低。
- 年轻代又分为 Eden 区和两个 Survivor 区(From Survivor 和 To Survivor)。新创建的对象在 Eden 区分配内存,当 Eden 区满时,进行一次 Minor GC(小型垃圾回收),将存活的对象复制到 Survivor 区。
-
老年代回收
- 老年代中的对象生命周期较长,一般采用标记 - 清除算法或标记 - 整理算法。当老年代满时,进行 Major GC(大型垃圾回收),通常会比 Minor GC 停顿时间更长。
-
永久代 / 元空间回收
- 主要回收类的元数据等信息,一般在 Full GC(全量垃圾回收)时进行。
Java 的垃圾回收机制是自动运行的,开发人员可以通过调整 JVM 参数来影响垃圾回收的行为,但通常不建议过度干预,以免影响程序的性能和稳定性。
5、jvm中四种引用的区别
一、强引用(Strong Reference)
-
定义与特点:
- 强引用是最常见的引用类型。如果一个对象具有强引用,那么垃圾回收器永远不会回收这个对象,即使内存空间不足。
- 只要强引用存在,垃圾回收器就永远不会回收被引用的对象。
-
使用场景:
- 在大多数情况下,我们创建的对象都是通过强引用来引用的。当我们明确知道需要长期使用某个对象时,使用强引用是合适的。
二、软引用(Soft Reference)
-
定义与特点:
- 软引用所指向的对象在内存充足的时候不会被回收,只有当内存不足时,才会被垃圾回收器回收。
- 软引用可以用来实现对内存敏感的缓存。当系统内存充足时,缓存中的对象可以继续存在,提高程序的性能;当系统内存不足时,垃圾回收器会回收软引用所指向的对象,释放内存。
-
使用场景:
- 图片缓存:在加载大量图片时,可以使用软引用来缓存已经加载过的图片。当内存不足时,系统会自动回收一些不常用的图片,以保证程序的正常运行。
三、弱引用(Weak Reference)
-
定义与特点:
- 弱引用的对象不会阻止垃圾回收器回收它所引用的对象。当垃圾回收器进行垃圾回收时,如果发现某个对象只有弱引用指向它,那么这个对象就会被回收。
- 弱引用通常用于那些不应该阻止垃圾回收的对象,例如临时对象或者在特定场景下使用的对象。
-
使用场景:
- 缓存:当需要缓存一些对象,但又不希望这些对象阻止垃圾回收时,可以使用弱引用来实现缓存。如果内存紧张,缓存中的对象可以被自动回收,从而避免内存泄漏。
- 大型对象的临时引用:对于一些占用大量内存的对象,如果只是在特定场景下临时使用,可以使用弱引用来避免这些对象长时间占用内存。
四、虚引用(Phantom Reference)
-
定义与特点:
- 虚引用也称为幽灵引用或幻影引用,它是最弱的一种引用类型。虚引用不会决定对象的生命周期,无论是否有虚引用存在,垃圾回收器都会回收被引用的对象。
- 虚引用的主要目的是在对象被回收时收到一个通知。
-
使用场景:
- 资源清理:可以使用虚引用来在对象被回收时进行一些资源清理工作,例如关闭文件、释放数据库连接等。
总之,JVM 中的四种引用类型各有特点,强引用是最常见的引用类型,软引用、弱引用和虚引用则用于不同的场景,以实现更灵活的内存管理和资源控制
5、怎么判断一个对象是否达到回收标准
在 Java 中,判断一个对象是否达到回收标准主要依据以下两个原则:
一、引用计数法(较少使用)
-
原理:
- 给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1;当引用失效时,计数器值就减 1。
- 当一个对象的引用计数器为 0 时,就认为这个对象可以被回收。
-
缺点:
- 无法解决循环引用的问题。例如两个对象相互引用,即使没有其他地方再引用它们,它们的引用计数器也不为 0,导致无法被回收。
二、可达性分析算法(主流方法)
-
原理:
- 以一系列称为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。
- 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,即可以被回收。
-
GC Roots 对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。例如正在执行的方法中的局部变量所引用的对象。
- 方法区中类静态属性引用的对象。比如静态变量所引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI(即 Java Native Interface)引用的对象。
一旦确定一个对象不可达,也不是立即就被回收,还要经过两次标记过程:
- 第一次标记:对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,此时会进行第一次标记,并判断该对象是否有必要执行 finalize () 方法。如果对象没有覆盖 finalize () 方法或者 finalize () 方法已经被虚拟机调用过,那么该对象会被视为没有必要执行 finalize () 方法,将直接被回收。
- 第二次标记:如果对象被判定有必要执行 finalize () 方法,那么这个对象会被放置在一个名为 F-Queue 的队列中,并由一条由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。在 finalize () 方法中对象可以重新与引用链上的任何一个对象建立关联,如果在这个过程中对象成功拯救自己(即重新与 GC Roots 建立关联),那么第二次标记时该对象将从 "即将回收" 的集合中移除;否则,该对象会被回收。