Java--基础知识之JVM

一、什么是JVM
1、概念
JVM,即Java Virtual Machine(Java虚拟机),是Java和的核心和基础,是在Java编译器和操作系统平台间的虚拟处理器。JVM是利用软件方法实现的抽象的、计算机基于下层的操作系统和硬件平台可以在上面执行Java程序的字节码程序。
2、特点
JVM有完善的硬件架构(如处理器、堆栈、寄存器),其存在是为了支持与操作系统无关,实现Java跨平台。
3、Java的跨平台性
真正跨平台的是Java程序而非JVM。不同平台下安装了不同版本的JVM。编写的Java源码在编译后生成class文件(字节码文件),JVM是负责将这些字节码文件翻译成特定平台下的机器码然后运行,即在不同平台下安装对应的JVM,就可以运行编写的Java程序。而这个过程中Java程序没有做任何改变,只是通过JVM在不同平台上运行罢了,可以说是“一次编译,多处运行”。
4、启动与消亡
JVM负责运行一个Java程序,当启动一个Java程序时,也产生一个虚拟机实例,当程序关闭时这个虚拟机实例也消亡。
JVM运行起点:Java虚拟机实例通过调用某个初始类的main方法来运行Java程序,这个main方法是共有的、静态的、返回值为void类型,并传入一个字符串数组作为参数。
5、两种线程
(1)守护线程:通常由虚拟机自己使用,比如执行垃圾收集任务的线程,此外Java程序也可以把创建的线程标记为守护线程。
(2)非守护线程:比如Java程序中main()的线程,只要有任何非守护线程在运行,则Java程序也在继续运行,当其中所有非守护线终止时,虚拟机实例终止。此外,若安全管理器允许也可以通过调用Runtime.exit()或System.exit()来退出程序。
二、JVM、JRE与JDK
1、JRE
Java Runtime Environment(Java运行环境),是Java平台,所有的Java程序在JRE下运行。
2、JDK
Java Development Kit(Java开发工具包),用于编译和调试Java程序,JDK工具也是Java程序,也需要在JRE下运行。因此为了保证JDK的独立与完整性,在安装JDK时也会安装JRE,即JDK的安装目录下会有JRE目录来存放JRE文件。
3、关系简图
在这里插入图片描述
三、JVM三个主要的子系统
1、类加载器子系统(ClassLoader Subsystem)
Java的dynamic class loading功能由ClassLoade子系统处理,类加载器子系统负责加载和链接。在运行时(不是编译时)、首次引用类时初始化类。
(1)加载
把class字节码文件从各个来源通过类加载器装载入内存中。
注:字节码来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译。
(2)校验
确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全,包括文件格式、元数据、字节码、符号引用的验证。主要是为了保证加载进来的字节流符合虚拟机规范,不造成安全问题。
(3)准备
为类变量分配内存,并将其初始化为默认值。此时为默认值,在初始化的时候才会给变量赋值,即在方法区中分配这些变量所使用的内存空间。主要是为类变量分配内存、赋初值,不是实例变量。
注:初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值(比如基本类型的初值默认0,引用类型null,常量的初值为代码中设置的值等)。
(4)解析
把类型中的符号引用转换为直接引用(虚拟机把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用),解析包括类或接口、字段、类方法、接口方法的解析。
注:①符号引用:即一个字符串,这个字符串给出了一些能够唯一识别一个方法、一个变量、一个类的相关信息;②直接引用:一个内存地址或一个偏移量。
举例:调用方法test(),这个方法的地址是12345,test是符号引用,12345是直接引用。
(5)初始化
是对类变量初始化,是执行类构造器的过程。只对static修饰的变量或语句进行初始化。如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
以下情况会主动初始化:
①使用new关键字创建对象会对类初始化(这个类未被初始化过)
②初始化类的时候,如果其父类没有被初始化过,则先初始化父类
③进行反射调用的时候
④虚拟机启动时先初始化main方法所在类
⑤调用类的静态属性或静态方法,或给类的静态属性赋值时
以下情况不进行初始化:
①如果已经初始化就不再进行初始化(同一个类加载器下面只能初始化类一次)
②编译时能确定下来的静态变量,不会对类进行初始化,例如final修饰的静态变量
总结:
(1)每个类加载器都维护了一份自己的名称空间,同一个名称空间里不能出现两个同名的类
(2)双亲委派机制
如果一个类加载器收到了一个类加载请求,它首先不会自己去加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它管理的范围之中没有这个类)时,子加载器才会尝试着自己去加载。
作用:避免重复加载,避免核心API被篡改,保证java平台的安全。
举例:可以自己写String类吗?不可以,类加载器有加载顺序,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个加载器已加载就视为已加载此类,保证此类所有ClassLoader加载一次。而加载的顺序是自顶向下,因此自己写的String是被Bootstrap ClassLoader加载了,所以Apps ClassLoader就不会再去加载自己写的String类了。
2、运行时数据区(Runtime Data Area)
(1)Method Area(方法区)
所有类级数据都将存储在这里,包括静态变量。每个JVM只有一个方法区域,它是一个共享资源。
(2)Heap Area(堆区)
所有对象及其对应的实例变量和数组都将存储在这里,每个JVM也只有一个堆区域。由于方法和堆区域为多个线程共享内存,因此存储的数据不是线程安全的。
(3)Stack Area(栈区)
对于每个线程,将创建一个单独的运行时堆栈;对于每个方法调用,都会在堆栈内存中生成一个条目,称为Stack Frame,所有本地变量都将在堆栈内存中创建,堆栈区域是线程安全的,因为它不是共享资源。
Jvm对该区域规范了两种异常:
①线程请求的栈深度大于虚拟机栈所允许的深度,将抛出StackOverFlowError。
②若虚拟机栈可动态扩展,当无法申请到足够内存空间时将抛出OutOfMemoryError。
堆栈框架分为三个子实体:
Local Variable Array:方法的局部变量及相应的值存储在这里
Operand stack:如果需要执行任何中间操作,操作数堆栈充当运行时工作区来执行操作
Frame data:与方法对应的所有符号都存储在这里。在任何异常情况下,catch块信息都将保存在frame data中
(4)PC Registers(PC寄存器)
每个线程将有单独的PC寄存器,以保持当前执行指令的地址一旦执行,PC寄存器将更新下一条指令。
(5)Native Method stacks(本地方法栈)
本地方法栈保存本地方法的信息。为每一个线程,将创建一个单独的本地方法栈。
3、执行引擎(Execution Engine)
分配给运行时数据区域的字节码将由执行引擎执行,执行引擎读取字节码并逐个执行。
(1)Interpreter(解释器)
解释器可以快速地解释字节码,但执行速度很慢。解释器的缺点是,当一个方法被多次调用时,每次都需要一个新的解释。
(2)JIT Compiler(编译器)
JIT编译器消除了解释器的缺点。执行引擎将在转换字节码时使用解释器的帮助,但是当它发现重复的代码时,它使用JIT编译器,JIT编译整个字节码并将其更改为本机代码。此本机代码将直接用于重复的方法调用,从而提高系统的性能。
JIT构成组件有:
Intermediate Code Generator:生成中间代码
Code Optimizer:负责优化生成的中间代码
Target Code Generator:负责生成机器代码/本机代码
Profiler – 特殊的组件,负责寻找 hotspots,即方法是否被多次调用
(3)Garbage Collector(垃圾回收)
收集和删除未引用的对象。可以通过调用System.gc()触发垃圾收集,但不能保证执行。JVM的垃圾收集收集创建的对象。
GC 分为两种:
①Minor GC:新生代(Young Gen)空间不足时触发收集,由于Java 中的大部分对象通常不需长久存活,新生代是GC收集频繁区域,所以采用复制算法。
注:复制算法
把内存空间划为两个相等的区域,每次只使用其中一个区域。GC时遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理。
不足之处:内存利用率问题;在对象存活率较高时,其效率会变低。
②Full GC:老年代(Old Gen )空间不足或元空间达到高水位线执行收集动作,由于存放大对象及长久存活下的对象,占用内存空间大,回收效率低,所以采用标记-清除算法。
注:标记-清除算法
分为两阶段:标记和清除。首先标记出哪些对象可被回收,在标记完成之后统一回收所有被标记的对象所占用的内存空间。
不足之处:无法处理循环引用的问题;效率不高;产生大量内存碎片,可能导致以后在分配大对象的时候而无法申请到足够的连续内存空间,导致提前触发新一轮GC。
四、JVM垃圾回收
1、什么是垃圾回收
将内存中不再被使用的对象进行回收。
2、GC原理
GC消耗资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能缩短GC对应用造成的暂停。对新生代的对象的收集称为minor GC,对旧生代的对象的收集称为Full GC(程序中主动调用System.gc()强制执行的GC为Full GC)。
3、不同情况下的GC情况
(1)强引用:这个对象的实例没有其他对象引用,GC时才会被回收(默认情况下,对象采用的均为强引用)。
(2)软引用:只有在内存不够用的情况下才会被GC(适合于缓存场景)。
(3)弱引用:GC时一定会被回收。
(4)虚引用:只用来得知对象是否被GC。
4、标记为垃圾的方法
(1)引用计数法
①概念
堆中每个对象实例都有一个引用计数,当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。
当任何其它变量被赋值为这个对象的引用时,计数加1(例:a = b,则b引用的对象实例的计数器加1)。但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。
任何引用计数器为0的对象实例可以被当作垃圾收集。
当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
②优点
可以很快执行,有利于需要不被长时间打断的实时环境。
③缺点
无法检测出循环引用,例:父对象有一个对子对象的引用,子对象反过来引用父对象,这样他们的引用计数永远不可能为0。
(2)可达性分析
①概念
在所有的引用关系中,从一个节点GC Roots开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余没有被引用到的节点,即无用节点,将会被判定为可回收对象。
②Java中可作为GC Roots的对象:虚拟机栈中引用的对象(栈帧中的本地变量表)、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(Native方法)引用的对象。
在这里插入图片描述
5、垃圾回收算法
(1)标记-清除算法
①概念
标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
②缺点
效率问题:标记和清除两个阶段的效率都不高,因为这两个阶段都需要遍历内存中的对象,很多时候内存中的对象实例数量是非常庞大的,很耗时。
空间问题:标记清除之后会产生大量不连续的内存碎片,可能导致以后程序运行过程中需要分配较大对象时,无法找到足够的连续内存,而不得不提前触发另一次垃圾回收。
(2)复制算法
①将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。
②优点
简单高效,优化了标记-清除算法的效率低、内存碎片多问题。
③缺点:
将内存缩小为原来的一半,浪费了一半的内存空间,代价太高。
如果对象存活率高则会很耗时(假设为100%,那么需要将所有存活的对象复制一遍)。
(3)标记-整理算法
①概念
不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。
②优点
弥补了标记-清除算法存在的内存碎片问题,并且消除了复制算法内存减半的高额代价。
③缺点
效率不高:不仅要标记存活对象,还要整理所有存活对象的引用地址。
(4)分代回收算法
①概念
按对象的存活周期不同将内存划分为几块,以根据各个年代的特点采用最合适的收集算法。
②特点
新生代:存活时间短。因为每次GC都有大量对象死去,因此采用复制算法收集,这样只需付出少量复制成本。
老年代:经过多次Minor GC而存活下来,存活周期长。因为其存活率高,如果采用复制算法则没有额外空间进行分配担保,因此采用标记-清除算法或者标记-整理算法收集。
6、空间分配担保机制
(1)内存分配
动态对象年龄判定:年龄超过阈值或survivor空间相同年龄所有对象大小总和大于survivor区一半,年龄大于或等于该年龄的对象直接进入老年代。分配原则:对象优先分配在Eden区、大对象直接进入老年代、长期存活的对象将进入老年代。
(2)执行Minor GC前,JVM会首先检查Tenured是否有足够的空间存放新生代尚存活对象,(新生代使用复制收集算法,为了提升内存利用率,只使用了其中一个Survivor作为轮换备份)当出现大量对象在Minor GC后仍然存活的情况时,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代(前提是老年代需要有足够的空间容纳这些存活对象)。
但存活对象的大小在实际完成GC前是无法明确知道的,因此Minor GC前, JVM会先先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小,可以则进行Minor GC,否则进行Full GC,让老年代腾出更多空间。
而取历次晋升的对象的平均大小也是有一定风险的,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败,老年代也无法存放这些对象了,此时只好在失败后重新Full GC,让老年代腾出更多空间。
7、几种垃圾回收器的特征
(1)Serial收集器:单线程收集;运行在Client端;简单高效。
(2)ParNew收集器:多线程收集;默认开启的收集线程数与CPU数量相同;可与CMS收集器配合。
(3)Parallel Scavenge收集器:吞吐量可控。
(4)Serial Old收集器:单线程收集;基于标记-整理算法;运行在Client端。
(5)Parallel Old收集器:多线程收集;基于标记-整理算法。
(6)CMS收集器:以获取最短回收停顿时间为目的;基于标记-清除算法;对CPU资源敏感。
(7)G1收集器:并行与并发;分代收集;空间整合;停顿时间可预测。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值