【Java】JVM执行流程、类加载过程和垃圾回收机制

这篇具有很好参考价值的文章主要介绍了【Java】JVM执行流程、类加载过程和垃圾回收机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

JVM,就是Java虚拟机,Java的的程序都是运行在JVM当中。

JVM执行流程

程序在执行之前先要把java源代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方(类加载器(ClassLoader)) 把文件加载到内存中的运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口(本地库接口(Native Interface))来实现整个程序的功能。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

执行引擎

将Java字节码转换成CPU指令。

本地方法接口

调用不同系统的API实现不同的功能。

运行时数据区

方法区

方法区中存放的是类对象,可以理解为模板。在《Java虚拟机规范中》把此区域称之为“方法区”,而在 HotSpot 虚拟机的实现中,在 JDK 7 时此区域叫做永久代(PermGen),JDK 8 中叫做元空间(Metaspace)。运行时常量池是方法区的一部分,存放字面量与符号引用。

JDK 1.8 元空间的变化
1.对于现在使用最最广泛的 HotSpot 来说,JDK 8 元空间的内存属于本地内存,这样元空间的大小就不在受 JVM 最大内存的参数影响了,而是与本地内存的大小有关。
2.JDK 8 中将字符串常量池移动到了堆中。

堆中存放的是new出来的具体对象。堆区和方法区之间是内存共享的:多个线程都可以去new对象,那么必须从方法区中拿对象的模板;每个线程创建出来的对象都会放在堆中。

虚拟机栈(线程私有)

栈主要记录的是 方法的调用关系和可能出现的栈溢出错误。 每一个线程都有对应的一个Java虚拟机栈,每调用一个方法都会以一个栈帧的形式加入到线程的栈中,方法执行完成之后栈帧就会被调出栈。此时可能存在一种情况,在递归调用时,调用的深度过深可能会出现栈溢出的错误。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

  1. 局部变量表: 存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小。简单来说就是存放方法参数和局部变量。
  2. 操作栈:每个方法会生成一个先进后出的操作栈。
  3. 动态连接:指向运行时常量池的方法引用。
  4. 方法返回地址:PC 寄存器的地址。

什么是线程私有?
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为"线程私有"的内存

本地方法栈(线程私有)

工作原理和Java虚拟机栈一样,记录的是本地方法的调用关系。

程序计数器(线程私有)

记录了当前线程的方法执行到了那一行(指令)。程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为空。

堆溢出问题

Java堆用于存储对象实例,不断的创建对象,就可能会在对象数量达到最大堆容量后就会产生内存溢出。
演示堆溢出现象:
设置JVM参数-Xms:设置堆的最小值、-Xmx:设置堆最大值。

public class HeapDemo {
    static class OOMObject {}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();

        // 不停的为list添加元素
        while (true) {
            list.add(new OOMObject());

        }
    }
}

【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言
当出现"Java heap space"则很明确的告知我们,OOM发生在堆上,此时堆内存被占满。此时需要优化堆内存的大小(通过调整-Xss参数来)来避免这个错误。

类加载

类加载的过程

对于一个类来说,它的生命周期是这样的:

【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

加载

加载就是读取.class文件。
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

连接

验证

这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。比如此时有这样一行代码:public static int value = 123;它是初始化 value 的 int 值为 0,而非 123。

解析

解析阶段是Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。

初始化

初始化阶段,Java 虚拟机真正开始执行类中编写的Java 代码,将控制权移交给应用程序。初始化阶段就是执行类构造器方法的过程。

双亲委派机制

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父
类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启
动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)
时,子加载器才会尝试自己去完成加载。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言
1.BootStrap:启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。 扩展类加载器。加载 lib/ext 目录下的类。
2.ExtClassLoader: 扩展类加载器,加载lib/ext目录下的类;
3.AppClassLoader:应用程序类加载器;
4.自定义加载器:根据自己的需求定制类加载器;

垃圾回收

public class GCDemo {
    public static void main(String[] args) {
        test();
    }

    private static void test() {
        Student student = new Student();
        System.out.println(student);
    }
}

对于上述这个实例,当test执行完成之后,就不会再使用了,所以对于这种无效的对象,将会被当作垃圾回收掉。如何标记这个对象是垃圾?

死亡对象的判断算法

引用计数算法

给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任
何时刻计数器为0的对象就是不能再被使用的,即对象已"死”。
但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的
循环引用问题

public class GCDemo01 {
    public Object instance = null;
    private static int _1MB = 1024 * 1024;
    private byte[] bigSize = new byte[2 * _1MB];
    public static void testGC() {
        GCDemo01 test1 = new GCDemo01();
        GCDemo01 test2 = new GCDemo01();
        test1.instance = test2;
        test2.instance = test1;
        test1 = null;
        test2 = null;
        // 强制jvm进行垃圾回收
        System.gc();
    }
    public static void main(String[] args) {
        testGC();
    }
}

比如上述代码,当test1=null;test=null时,那么test1和test2中的instance再也无法访问到,所以此时堆中对象的引用计数无法归零,导致无法垃圾回收。

可达性分析算法

通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。Java中就采用了"可达性分析"来判断对象是否存活
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言
在Java语言中,可作为GC Roots的对象包含下面几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
2.方法区中类静态属性引用的对象;
3.方法区中常量引用的对象;
4.本地方法栈中 JNI(Native方法)引用的对象。

从上面我们可以看出“引用”的功能,除了最早我们使用它(引用)来查找对象,现在我们还可以使用“引用”来判断死亡对象了。所以在 JDK1.2 时,Java 对引用的概念做了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减。

1.强引用:类似于Student student = new Student()这种引用,会经历正常的GC,判定为死亡时会被回收;
2.软引用:软引用是用来描述一些还有用但是不是必须的对象,当系统内存不够或者触发阈值时会被回收;
3.弱引用:弱引用也是用来描述非必需对象的。在每次新生代GC时都会回收弱引用;
4.虚引用:只是在对象被回收时,收到一个通知。

垃圾回收的过程

通过上面的学习可以将死亡对象在堆中标记出来,标记出来之后就可以进行垃圾回收操作。先来看一下堆的结构:
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言
HotSpot默认新生代与老年代的比例是1:2,新生代中Eden区与Survivor区的大小比例是8:1,也就是说Eden:Survivor From(S0):Survivor To(S1)=8:1:1。所有新new出来的对象全都在Eden区。每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。

回收过程如下:
1.当Eden区满的时候会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor gc的时候会扫描Eden区和From区域对两个区域进行垃圾回收。经过这次回收后还存活的对象则直接复制到To区域并将Eden和From区域清空。
2.当后续Eden又发生Minor gc的时候会对Eden和To区域进行垃圾回收存活的对象复制到From区域并将Eden和To区域清空。
3.部分对象会在From和To区域中来回复制,如此交换15次(由JVM参数MaxTen),最终如果还存活就将其放到老年代中。

新生代:一般创建的对象都会进入新生代;
老年代:大对象和经历了N次(默认值是15)垃圾回收依然存活下来的对象会从新生代移动到老年代。

新生代的GC称为Minor GC ,老年代的GC称为Full GC或Major GC。

每次进行垃圾回收的时候,程序都会进入暂停状态(STW),STOP THE WORLD。 为了高效的扫描内存区域,缩短程序暂停的时间,有一系列的垃圾回收算法。

标记-清除算法

"标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
"标记-清除"算法的不足主要有两个 :
1.效率问题 : 标记和清除这两个过程的效率都不高
2.空间问题 : 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中
需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

复制算法

"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。HotSpot在S0和S1区使用的就是这种算法
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

标记-整理算法

"标记-整理算法"主要应用于老年代。标记过程仍与"标记-清除"过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言
缺点是在回收之后多了一步整理内存的工作;优点是可以有大量连续的内存空间。

在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-整理"算法。

垃圾收集器

垃圾收集算法是内存回收的方法论,而垃圾收集器就是内存回收的具体实现。垃圾收集器的作用:垃圾收集器是为了保证程序能够正常、持久运行的一种技术,它是将程序中不用的死亡对象也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存空间。垃圾收集器的不断更新就是为了减少STW
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

Serial

Serial 收集器是最基本、发展历史最悠久的串行GC收集器。它是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

ParNew

ParNew是对Serial优化了的并行GC。用多线程的方式扫描内存,提高垃圾回收的效率,减少STW的时间。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

Parallel Scavenge

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
与前面的区别在于,它采用了GC自适应的调节策略:

Parallel Scavenge收集器有一个参数- XX:+UseAdaptiveSizePolicy 。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了, 虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。

Serial Old

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

CMS

CMS是一种老年代并发GC。 与之前的方法不同的是,它使用了三色标记算法。它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤:初始标记(CMS initial mark)、并发标记(CMS concurrent mark)、重新标记(CMS remark)、并发清除(CMS concurrent sweep)。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言

G1

G1(Garbage First)垃圾回收器是用在heap memory很大的情况下,在内存区域的划分上不再像之前的新生代和老年代一样,而是把heap划分为很多很多的region块,然后并行的对其进行垃圾回收,从而提高效率。
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言
图中一个region有可能属于Eden,Survivor或者Tenured内存区域。图中的E表示该region属于Eden内存区
域,S表示属于Survivor内存区域,T表示属于Tenured内存区域。图中空白的表示未使用的内存空间。
G1垃圾收集器还增加了一种新的内存区域,叫做Humongous(大对象)内存区域,如图中的H块。这种内存区域主要用于存储大对象-即大小超过一个region大小的50%的对象。

1.年轻代
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法。把Eden区和Survivor区的对象复制到新的Survivor区域。

2.老年代
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样。

一个对象的一生
我是一个普通的 Java 对象,我出生在 Eden 区,在 Eden 区我还看到和我长的很像的小兄弟,我们在 Eden 区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了 Survivor 区的 “From” 区(S0 区),自从去了 Survivor 区,我就开始漂了,有时候在 Survivor 的 “From” 区,有时候在 Survivor 的 “To” 区(S1 区),居无定所。直到我 18 岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在老年代里,我生活了很多年(每次GC加一岁)然后被回收了。


继续加油~
【Java】JVM执行流程、类加载过程和垃圾回收机制,JavaEE初阶,java,jvm,java-ee,开发语言文章来源地址https://www.toymoban.com/news/detail-562851.html

到了这里,关于【Java】JVM执行流程、类加载过程和垃圾回收机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JVM解密: 解构类加载与GC垃圾回收机制

    JVM 其实是一个 Java 进程,该进程会从操作系统中申请一大块内存区域,提供给 Java 代码使用,申请的内存区域会进一步做出划分,给出不同的用途。 其中最核心的是栈,堆,方法区这几个区域: 堆,用来放置 new 出来的对象,类成员变量。 栈,维护方法之间的调用关系,放

    2024年02月10日
    浏览(45)
  • 【Java虚拟机】JVM垃圾回收机制和常见回收算法原理

    1.垃圾回收机制 (1)什么是垃圾回收机制(Garbage Collection, 简称GC) 指自动管理动态分配的内存空间的机制,自动回收不再使用的内存,以避免内存泄漏和内存溢出的问题 最早是在1960年代提出的,程序员需要手动管理内存的分配和释放 这往往会导致内存泄漏和内存溢出等问

    2024年02月02日
    浏览(53)
  • JVM及垃圾回收机制

    类加载器负责将.class文件加载到JVM中。主要分为三种层次:Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。它们按层次关系加载类,保证类的隔离性和重用性。 运行时数据区包括方法区、堆、栈、本地方法栈和程序计数器。堆用于存放对象实例,方法区保存类信息和

    2024年02月12日
    浏览(41)
  • 【JVM】垃圾回收机制

     哈喽,哈喽,大家好~ 我是你们的老朋友: 保护小周ღ    今天给大家带来的是  JVM (Java 虚拟机) 的垃圾回收机制,回收是指回收什么?  如何确定要回收的内存: 引用计数,可达性分析,如何释放空间 : 标记清除,复制算法,标记整理,分代回收 ,一起来看看叭~ 本期

    2024年02月09日
    浏览(45)
  • 浅谈JVM垃圾回收机制

    新生代收集(Minor GC/Young GC):只对新生代进行垃圾收集 老年代收集(Major GC/Old GC):只队老年代进行垃圾收集 混合收集(Mixed GC):对整个新生代和老年代进行垃圾收集 收集整个Java堆和方法区 空间分配担保是为了确保在Minor GC之前老年代还有容纳新生代所有对象的剩余空间 垃圾回收算

    2024年02月10日
    浏览(43)
  • JVM基础(3)——JVM垃圾回收机制

    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖,挖的越深,基础越扎实! 阶段1、深入多线程 阶段2、深入多线程设计模式 阶段3、深入juc源码解析

    2024年01月25日
    浏览(47)
  • java面经03-虚拟机篇-jvm内存结构&垃圾回收、内存溢出&类加载、引用&悲观锁&HashTable、引用&finalize

    要求 掌握 JVM 内存结构划分 尤其要知道方法区、永久代、元空间的关系 结合一段 java 代码的执行理解内存划分 执行 javac 命令编译源代码为字节码 执行 java 命令 创建 JVM,调用类加载子系统加载 class,将类的信息存入 方法区 创建 main 线程,使用的内存区域是 JVM 虚拟机栈 ,

    2024年02月09日
    浏览(57)
  • JVM——类加载和垃圾回收

    目录 前言 JVM简介 JVM内存区域划分 JVM的类加载机制 1.加载 双亲委派模型 2.验证 验证选项 3.准备 4.解析 5.初始化 触发类加载 JVM的垃圾回收策略 GC 一:找     谁是垃圾  1.引用计数 2.可达性分析  (这个方案是Java采取的方案)。 二:释放垃圾对象 三种典型的策略 JVM实现思

    2024年02月16日
    浏览(36)
  • JVM中的垃圾回收机制

    java相较于c、c++语言的优势之一是自带垃圾回收器,垃圾回收是指 不定时 去堆内存中清理 不可达 对象。不可达的对象并不会 马上 就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的就是通过调用System.gc 方法来建议执行垃圾收集

    2024年02月16日
    浏览(49)
  • JVM:垃圾回收机制(GC)

    引用计数算法:         在对象中添加一个引用计数器,当每有一个地方引用它时,计数器值加一。当引用失效时,计数器值就减一。当一个对象的计数器为零时,表示该对象没有被任何其他对象引用,因此可以被释放。 优点 :是可以及时回收垃圾对象,避免内存泄漏,且

    2024年01月19日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包