初识JVM

一:JVM简介

JVM是Java Virtual Machine的简称,意为Java虚拟机


虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统

二:JVM内存分布

(1)定义

一个进程在运行过程中,要从操作系统这里申请一定的资源;而JVM也是如此,JVM会搞出一大块内存,供Java代码运行时使用;且JVM会再次把这一大块内存分割成几块区域,作为不同的用途

(2)内存分布图

初识JVM_第1张图片

(3)堆(heap)

①JVM内存分布中最大的内存区域


②凡是使用new创建的对象都在堆上保存,还有成员变量


③堆是虽然程序开始运行时创建,随着程序退出而销毁,只要堆中数据还在就不会被销毁

(4)方法区(Method Area)

①存储的是类(.class文件)、方法内容、static静态变量、常量.....


②方法编译出的字节码就是保存在这里

(5)程序计数器(PC Register)

只是一块很小的空间;用来保存下一条执行的指令的内存地址


栈和程序计数器是每个线程都有一份的,每个线程都有自己的执行逻辑,有了程序计数器就能知道自己执行到哪里了,有了栈就可以记录方法的调用关系

(6)虚拟机栈(JVM Stack)

①存储与方法调用相关的一些信息


②每个方法被调用、执行或递归时都会创建一个栈帧,栈帧里面存储的有局部变量表、操作数栈、动态链接、返回地址


③当方法结束后,栈帧就会被销毁,栈帧保存的数据也就被销毁了

(7)本地方法栈(Native Method Stack)

与虚拟机栈作用类似,但是保存的内容时Native方法的局部变量

(有些JVM中,本地方法栈和虚拟机栈时一起的,比如hotspot)

三:JVM类加载机制

(1)JVM类加载简介

JVM类加载,通俗来讲,就是把类(.class文件)从硬盘加载到内存中去


初识JVM_第2张图片

(2)JVM类加载大致过程

1.加载

JVM类加载的第一步

(这个加载只是JVM类加载的一个小环节,不是整个加载流程)


作用:先找到.class文件,读取文件内容,并尝试解析格式

2.验证

JVM类加载的第二步


作用:检查一下当前的.class文件是否符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全

3.准备

JVM类加载的第三步


作用:给类对象分配内存空间,其里面内容的值全为0

(为最终目标做准备,因为我们的最终目标就是构造出完整的类对象,完整的类对象就包括分配内存+初始化,这里我们已经分配好了内存)

初识JVM_第3张图片

4.解析

JVM类加载的第四步


作用:主要是初始化类对象涉及中的一些字符串常量

(Java虚拟机将常量池内的符号引用替换为直接引用的过程)

初识JVM_第4张图片

5.初始化

JVM类加载的第五步


作用:对类对象进行更具体的初始化操作

(比如:初始化静态成员、执行静态代码块、加载父类.....)

初识JVM_第5张图片

(3)双亲委派模型

1.作用

描述类加载的过程中,如何找到.class文件


说是"双亲",其实是“单亲”或者“父亲”更为准确

2.类加载器

JVM加载.class文件时,需要用到“类加载器”模块


在JVM中,就自带三个类加载器

(当然,程序员也可以自己写类加载器)

3.JVM自带的三个类加载器

①Bootstrap ClassLoader:负责加载Java标准库中的类


②Extension ClassLoader:负责加载JVM扩展的库

(除了Java标准库之外,实现JVM的厂商可能还会添加一些类)


③Applicaiton ClassLoader:负责加载第三方库

(比如mysql jdbc driver、servlet、jackson;还有我们代码自己写的类)


这三个类加载器就存在父子关系

(当然这里的父子关系并非是父类子类的继承,而是对象里有一个parent引用指向父类加载器实例)


Applicaiton ClassLoader的父亲就是Extension ClassLoader

Extension ClassLoader的父亲就是Bootstrap ClassLoader

Bootstrap ClassLoader没有父亲

4.双亲委派模型的流程

总结:先父后子,父没有再交给子

(说白了,双亲委派模型就是找文件的过程)


加载的优先级

标准库的类优先加载,其次到扩展库,最后是第三方库

比如:假设标准库有一个自己的类java.lang.tostring,而我们自己又写了一个类java.lang.tostring,那么就会优先找标准库里的类


①先执行Applicaiton ClassLoader,但它不会立即就搜索第三方库的目录,而是先把加载的任务委派给父亲,让它的父亲Extension ClassLoader先尝试加载


②此时需要执行Extension ClassLoader,但它也不会立即就搜索扩展库的目录,而是把加载的任务委派给父亲,让它的父亲Bootstrap ClassLoader先尝试加载


③这时候就到了Bootstrap ClassLoader执行,但是Bootstrap ClassLoader没有父亲,只能自己动手来搜索类了

(1)如果找到了类,就会进行后续的加载,此时就跟Extension ClassLoader和Applicaiton ClassLoader都没关系了,就不会加载到后面了

(2)如果没有找到这个类,任务就会交回给Extension ClassLoader孩子去完成


④如果Bootstrap ClassLoader没有找到这个类,就把任务交给孩子Extension ClassLoader去完成,此时Extension ClassLoader就要去搜索扩展库目录看看有没有这个类

(1)如果找到了类,就会进行后续的加载,此时就跟Applicaiton ClassLoader没关系了

(2)如果没有找到这个类,任务就会交回给Applicaiton ClassLoader孩子去完成


⑤如果Extension ClassLoader没有找到这个类,就会把任务交给孩子Applicaiton ClassLoader去完成,此时Applicaiton ClassLoader就去第三方库目录去看看有没有这个类

(1)如果找到了类,就会进行后续的加载

(2)如果没有找到这个类,就会抛出异常

四:JVM垃圾回收机制(GC)

(1)定义

JVM会自动的判定某个内存是否会继续使用,如果不会,就把这个内存当作"垃圾",然后自动的把这个内存给释放

(2)不同编程语言的内存释放机制

①C语言:完全靠程序员手动释放

(缺点:非常不靠谱)


②C++:引入了"智能指针",一定程度上解决了C语言的问题


③Java/Python/Go/PHP/Ruby:引入了垃圾回收机制,最好的解决办法,最大程度的解放了程序员

(缺点:消耗额外的系统资源,消耗一定的时间,可能带有STW问题)

关于STW问题,可参考大佬写的文章:JVM的STW(stop the world)机制及调优案例


④Rust:进行强编译期检查;在编译过程中,智能的分析你的代码,看你代码中在哪里插入释放语句比较合适,自动给你插入,不用程序员自己写

(缺点:Rust的语法很复杂)

(3)JVM垃圾回收解析

1.回收的是什么

回收的是对象;以对象为单位进行回收


GC并非是判定哪几个字节需要回收,而是去判定对象是不是垃圾,进一步进行回收

2.内存分布的对象哪些该回收或不该回收

①栈空间

栈空间不需要GC回收,因为栈里面包含很多"栈帧",每个"栈帧"对应一个方法,该方法执行结束,此时这个栈帧就销毁了,栈帧上的局部变量啥的自动销毁,也就不需要GC回收了


②程序计数器

程序计数器不需要GC回收,线程销毁,自然就跟着销毁


③方法区

很少会涉及到对象的卸载,一般都是只进不出


④堆

GC回收主要是针对堆而言,上文我们说过凡是new出来的对象都在堆上存储

3.垃圾回收的步骤

步骤一:判定对象是否为垃圾

如何判定:如果一个对象在后续的代码中没有任何引用指向它,即认为该对象是垃圾

(下文会说到如何更详细的判定!!!)

初识JVM_第6张图片

初识JVM_第7张图片


步骤二:进行内存释放,清理垃圾

(下文会说到如何更详细的清理,包含了垃圾回收算法)

(4)JVM如何判定垃圾

1.引用计数

方法:给这个对象里面安排一个计数器,每次有引用指向它,计数器+1;每次引用被销毁,计数器-1;当计数器为0时,就认为该对象是垃圾


注意:这个引用计数并非是JVM中使用的判定方式


初识JVM_第8张图片


初识JVM_第9张图片


初识JVM_第10张图片

2.可达性分析

 方法:JVM首先会从现有代码中能直接访问到的引用出发,尝试遍历所有能访问的对象,只要对象能访问到,就会标记成"可达";完成整个遍历之后,"可达"之外的对象,就会标记为"不可达",也就相当于是垃圾


注意:这个可达性分析是JVM中使用的判定方式


初识JVM_第11张图片


初识JVM_第12张图片

3.两者的区别

初识JVM_第13张图片

(5)JVM如何清理垃圾

1.标记清除算法

标记清除算法是最基础的收集算法


算法分为"标记"和"清除"两个阶段

①首先标记出所有需要回收的对象

②在标记完成后统一回收所有被标记的对象


标记后直接进行释放


缺点:直接进行释放对象,可能会引起"内存碎片";因为我们在申请内存的时候,都是申请的“连续”的内存空间,释放内存就可能会破坏原有的连续性,导致内存不连续了,即有内存但是申请不了

初识JVM_第14张图片

2.复制算法

复制算法是为了解决标记清除算法的效率问题


本质就是通过冗余的内存空间,把有效对象复制到另一部分的空间,从而避免内存碎片


初识JVM_第15张图片


缺点:复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低

3.标记整理算法

标记过程仍与"标记清除算法"过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存


初识JVM_第16张图片


缺点:这种有效对象往前移的成本其实也是挺高的

4.分代算法

①Java代码中对象的分类

(1)生命周期特别短的对象

(2)生命周期特别长的对象


②分代算法的本质:按照对象的年龄来制定不同的回收策略

(1)新生代:进行复制算法

原因:每一轮GC留下来的有效对象都不多,复制算法的开销不大

(2)老年代:进行标记清除算法和标记整理算法

原因:不会太有对象真销毁;此时标记整理的开销也不大

(特殊情况:如果这个对象体积特别大,就会直接进入老年代)


初识JVM_第17张图片

五:垃圾收集器

如果说上面我们讲的收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现

(1)作用

垃圾收集器的作用:垃圾收集器是为了保证程序能够正常、持久运行的一种技术,它是将程序中不用的 死亡对象也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存空间

(2)重要的垃圾收集器

这些只是部分的垃圾收集器,并不是全部;随着时代的发展,不停的会有新的收集器诞生,也会有旧的消亡


初识JVM_第18张图片

你可能感兴趣的:(Java-EE,jvm)