一、JDK、JRE、JVM的区别
JDK: 全称Java Development Kit,是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的Java应用程序。JDK是整个Java开发的核心。
JRE: JRE,全称Java Runtime Environment,是指Java的运行环境,是可以在其上运行、测试和传输应用程序的Java平台。
JVM: JVM,全称Java Virtual Machine(Java虚拟机),是一种用于计算设备的规范,它是一个虚构出来的计算机,引入JVM后,Java语言在不同平台上运行时不需要重新编译。JVM是Java跨平台的核心。它在我们后面的内存优化,程序运行等方面都有重要的地位。
二、深入学习JVM
2.1 jvm的结构
而我们常用的区域主要有:方法区、Java栈和Java堆
1、方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。
常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区。
2、Java Stack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。
最典型的 Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该
方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。
3、Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。
堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在 heap 中分配。
我们每天都在写代码,每天都在使用 JVM 的内存。
2.2 Java内存分配
1、基础数据类型直接在栈空间分配;
2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
3、引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
5、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆
空间区域等待 GC 回收;
6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
7、字符串常量在 DATA 区域分配 ,this 在堆空间分配;
8、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
2.3 jvm类加载过程
JVM类加载过程如下图:
JVM(Java虚拟机)在运行Java程序时,会按照一定的类加载过程加载和初始化类。类加载过程分为以下三个阶段:加载、连接和初始化。
加载(Loading):类加载的第一个阶段是加载阶段。在加载阶段,JVM会查找并读取类的字节码,并将其加载到内存中。加载过程可分为以下步骤:
通过类的全限定名(包名 + 类名)查找字节码文件。
将字节码文件读入到JVM,并转换为内部数据表示形式。
创建一个与类相关联的 java.lang.Class 对象,用来封装类的各种元数据信息。
将该 Class 对象放入方法区(也称为元空间)。
连接(Linking): 连接阶段是类加载的第二个阶段。
连接过程分为三个阶段:
- 验证(Verification): 验证阶段是确保被加载的类符合JVM规范的过程。在验证阶段,JVM会对字节码进行各种校验,防止恶意代码或非法代码被加载和执行。
- 准备(Preparation): 准备阶段是为类的静态变量(被 static 修饰的变量)分配内存并设置初始值的过程。这些变量被置为对应数据类型的默认值(0、false、null等)。
- 解析(Resolution): 解析阶段是将类、接口、方法和字段的符号引用替换为直接引用的过程。符号引用是以符号形式描述的引用,而直接引用是可以直接指向目标的内存地址。
初始化(Initialization): 初始化阶段是类加载的最后一个阶段。在初始化阶段,JVM会对类进行初始化操作,包括执行静态变量的赋值和静态代码块的执行。初始化阶段是在实际使用/访问类、接口、字段或方法时触发的,或者通过调用 Class.forName() 方法加载类。
总结: JVM的类加载过程包括加载、连接和初始化阶段。加载阶段将字节码文件加载到内存中,连接阶段包括验证、准备和解析,而初始化阶段执行类的初始化操作。这个过程保证了类加载时的安全和正确性,并在合适的时机初始化类,使得Java程序能够正常运行。
三、垃圾回收机制和常见算法
3.1 GC算法
根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链
(Reference Chain),当一个对象没有被 GC Roots 的引用链连接的时候,说明这个对象是不可用的。
GC Roots 对象包括:
a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b) 方法区域中的类静态属性引用的对象。
c) 方法区域中常量引用的对象。
d) 本地方法栈中 JNI(Native 方法)的引用的对象。
通过上面的算法搜索到无用对象之后,就是回收过程,回收算法如下:
3.2 标记—清除算法(Mark-Sweep)(DVM 使用的算法)
标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶段
紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。
3.3 复制算法(Copying)
复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,
然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的 JVM 用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是 1:1(大概是 8:1)。
3.4 标记—整理算法(Mark-Compact)
标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对
象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。
3.5 分代收集(Generational Collection)
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。
四、Java 中引用类型都有哪些?(重要)
Java 中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
4.1 强引用(StrongReference)
这个就不多说,我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不
会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意
回收具有强引用的对象来解决内存不足问题。
Java 的对象是位于 heap 中的,heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到
达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。
1. String abc=new String("abc"); //1
2. SoftReference<String> softRef=new SoftReference<String>(abc); //2
3. WeakReference<String> weakRef = new WeakReference<String>(abc); //3
4. abc=null; //4
5. softRef.clear();//5
第一行在 heap 堆中创建内容为“abc”的对象,并建立 abc 到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对 heap 中对象的软引用和弱引用,此时 heap 中的 abc 对象已经有 3 个引用,显然此
时 abc 对象仍是强可及的。
第四行之后 heap 中对象不再是强可及的,变成软可及的。
第五行执行之后变成弱可及的。
4.2 软引用(SoftReference)
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java 虚
拟机就会把这个软引用加入到与之关联的引用队列中。
软引用是主要用于内存敏感的高速缓存。在 jvm 报告内存不足之前会清除所有的软引用,这样以来 gc 就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于 gc 的算法和 gc 运行时可用内存的大小。当 gc 决定要收集软引用时执行以下过程,以上面的 softRef 为例:
- 首先将 softRef 的 referent(abc)设置为 null,不再引用 heap 中的 new
String(“abc”)对象。 - 将 heap 中的 new
String(“abc”)对象设置为可结束的(finalizable)。 - 当 heap 中的 new
String(“abc”)对象的 finalize()方法被运行而且该对象占用的内存被释放, softRef被添加到它的
ReferenceQueue(如果有的话)中。
注意: 对 ReferenceQueue 软引用和弱引用可以有可无,但是虚引用必须有。
被 Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存
不足且没有 Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来
SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误 (OutOfMemoryError)。
4.3 弱引用(WeakReference)
如果一个对象只具有弱引用,那该类就是可有可无的对象,因为只要该对象被 gc 扫描到了随时都会把它干
掉。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖
的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,
由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚
拟机就会把这个弱引用加入到与之关联的引用队列中。
4.4 虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对
象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃
圾回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回
收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联
的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
建立虚引用之后通过 get 方法返回结果始终为 null,通过源代码你会发现,虚引用通向会把引用的对象写进
referent,只是 get 方法返回结果为 null。先看一下和 gc 交互的过程再说一下他的作用。
- 不把 referent 设置为 null, 直接把 heap 中的 new String(“abc”)对象设置为可结束的(finalizable)。
- 与软引用和弱引用不同, 先把PhantomRefrence 对象添加到它的 ReferenceQueue 中.然后在释放虚可及的对象。
注意 Java中的引用类型通常会和GC算法一起使用,决定了对象是否会被回收!!!!!!