建筑行业网站模板,公司网站宣传自己做的灯展,电子商务网站调研,衡阳县建设局网站Java虚拟机#xff08;JVM, Java Virtual Machine#xff09;是运行Java应用程序的核心组件#xff0c;它是一个抽象化的计算机系统模型#xff0c;为Java字节码提供运行环境。JVM的主要功能包括#xff1a;类加载机制、内存管理、垃圾回收、指令解释与执行、异常处理与安…Java虚拟机JVM, Java Virtual Machine是运行Java应用程序的核心组件它是一个抽象化的计算机系统模型为Java字节码提供运行环境。JVM的主要功能包括类加载机制、内存管理、垃圾回收、指令解释与执行、异常处理与安全检查、性能优化等。
目录
一、初识JVM
1.1、JVM基础知识
1.2、JVM的组成部分
二、字节码文件详解
2.1、字节码文件的基本组成
2.2、类加载器
三、JVM的内存区域
四、JVM的垃圾回收机制
4.1、自动垃圾回收
4.2、方法区的回收
4.3、堆内存的回收 一、初识JVM
1.1、JVM基础知识
1.什么是JVM
JVM是java虚拟机本质上是运行在计算机上的程序它的职责是运行Java字节码文件。
正常我们编写的是.java文件通过javac进行编译成.class的字节码文件然后由JVM进行加载并解释成为机器码。
2.JVM的三大功能
JVM的第一个作用是类加载并对对字节码文件的指令进行解释成机器码。
JVM的第二个功能是内存管理主要包括为对象分配内存以及垃圾回收等。
JVM的第三个功能是即时编译对热点代码进行优化提升执行效率。
java语言是先编译成字节码文件再通过jvm解释成可执行的机器码文件。C/C是直接编译链接成可执行的.exe文件所以一般来说C/C语言的性能更高。
Java语言的实时解释主要是为了实现跨平台特性相当于一次编译成字节码文件可以不同的平台通过JVM进行解释运行。
3.常见的JVM有哪些
常见的虚拟机有如下几种其中HotSpot是JDK默认自带的虚拟机。 1.2、JVM的组成部分
JVM主要包括如下几部分类加载器、运行时数据区、执行引擎、本地接口四个部分。
1.类加载器加载class字节码文件中的内容到内存中。
2.运行时数据区负责管理JVM所使用的内存比如创建对象和销毁对象。
3.执行引擎将字节码中的文件解释成机器码同时使用即时编译器优化性能。
4.本地接口调用本地已经编译的方法比如虚拟机中提供的c/c方法。
其中运行时数据区包括大部分
堆存储对象实例和数组是所有线程共享的一块区域也是垃圾回收的主要区域。虚拟机栈每个线程都有自己的栈空间用于存放基本类型的变量、对象引用以及方法调用的上下文信息如局部变量表、操作数栈等。方法区存储类元数据如类的结构信息、静态变量、常量池和即时编译后的代码等。程序计数器记录当前线程执行字节码的位置。本地方法栈为JNI调用的本地方法服务。 二、字节码文件详解
2.1、字节码文件的基本组成
注意直接使用记事本打开字节码文件会是乱码一般来说使用jclasslib工具可以查看字节码文件
字节码文件主要包含如下5个部分
1.基础信息魔数、字节码对应的Java版本号、访问标识、父类与接口。
2.常量池保存了字符串常量、类或者接口名主要在字节码指令中使用。
3.字段当前类或者接口声明的字段信息。
4.方法当前类或者接口声明的方法信息。
5.属性类的属性比如源码的文件名、内部类的列表等。
2.2、类加载器
1.类的生命周期
加载-连接(验证、准备、解析)-初始化-使用-卸载
加载类加载器根据类的全限名通过不同的渠道以二进制流的方式获取字节码信息。类加载完成后JVM会把字节码信息放到方法区中。Java虚拟机还会在堆中生成一份与方法区中类似的java.lang.Class。
连接验证阶段是验证内容是否满足java虚拟机规范准备阶段是静态变量赋初值解析阶段是将常量池中的符号引用替换成指向内存的直接引用。
验证
1.对文件 格式的校验以及主次版本号是否满足当前Java虚拟机版本要求。例如主版本号不允许超过运行环境的版本号如果主版本号相等副版本号也不能超过。
2.元信息验证比如类必须有父类。
3.验证程序执行指令的语义。
4.符号引用验证例如是否访问了其他类中private修饰的方法。
准备
final修饰的基本数据类型的静态变量准备阶段会对变量赋终值。
解析
将常量池中的符号引用替换为直接引用。
符号引用在字节码文件中使用编号来访问常量池中的内容。
直接引用不再使用编号使用内存中的地址进行具体数据的访问。
初始化
初始化阶段会执行静态代码块中的代码并为静态变量赋初值。本质上就是初始化阶段会执行字节码文件中cliniit部分的指令。
以下几种方式会导致类的初始化
1.访问一个类的静态变量或者静态方法注意final修饰的常量不会触发初始化 。
2.调用Class.forName()
3.new一个该类的对象时
4.执行Main方法中的当前类 2.常见的类加载器有哪些
类加载器是Java虚拟机提供给应用程序去实现获取类或接口的字节码数据的技术。
启动类加载器(BootStrap ClassLoader)用于加载一些核心类,由HotSpot虚拟机提供使用 C编写的类加载器。
扩展类加载器器用于加载一些扩展类由JDK提供使用Java编写的类加载器。
应用程序类加载器用于加载应用第三方jar包里的类由JDK提供使用Java编写的类加载器。 3.双亲委派机制的作用什么是双亲委派机制
作用通过双亲委派机制避免恶意代码替换JDK中的核心类库确保核心类库的完整性和安全性另外可以避免同一个类被多次加载。
双亲委派机制类加载器在加载之前会向上查找其父类是否加载过如果父类加载过则直接返回父类能未加载过则判断能否加载(是否在类加载器的加载目录路径中)能加载就加载不能加载则由子类加载。 4.如何打破双亲委派机制为什么要打破双亲委派机制
打破双亲委派机制的三种方式 1.自定义类加载器并重写LoadClass方法就可以把双亲委派机制的代码清除
2.利用上下文类加载器加载类例如JDBC和 JNDI等
3.利用Osgi框架的类加载器
实现特殊的需求在某些情况下可能需要加载特殊的类或者自定义类加载逻辑例如实现热替换HotSwapping功能或者实现隔离的类加载环境如OSGi、Tomcat容器等这些场景下需要自定义类加载器并改变类加载的顺序和规则。实现版本隔离或多重类加载在某些框架或应用服务器中为了支持多个版本的同一类库共存或者为了实现模块化加载需要打破双亲委派让每个模块拥有独立的类加载器来加载自己的类。 三、JVM的内存区域
内存溢出OOM程序在使用某一块内存区域时存放的数据占用内存大小超出了虚拟机所能提供的最大内存上限。
1.Java的内存区域
程序计数器(线程不共享)每个线程通过程序计数器记录当前要执行的字节码指令的地址。在程序执行过程中程序计数器会记录下一行字节码指令的地址执行完当前指令后java虚拟机的执行引擎会根据指令计数器执行下一行 指令。具体来说程序计数器有两个作用1.记录指令执行的地址并控制线程执行指令。2.多线程执行指令线程切换时候用来记录线程执行到指令的地址。
程序计数器不会产生内存溢出因为每个线程只是存储一个固定长度的内存地址。
Java虚拟机栈(线程不共享)Java虚拟机栈随着线程的创建而创建随着线程的销毁而回收。java虚拟机栈存放的是栈帧栈帧包含三部分局部变量表、操作数栈、帧数据。
局部变量表程序在运行过程中存放执行方法的所有局部变量。局部变量表本质上是一个数组数组中每个位置称为一个槽long和double占2个槽其它类型占1个槽。局部变量保存的内容实例方法的this方法的参数方法体中声明的局部变量。为了节省空间局部变量表的槽是可以复用的一旦某个局部变量失效当前的槽就可以被复用。
操作数栈是虚拟机在执行指令的过程用来存放临时数据的一块区域。比如当前指令的值压入操作数栈中后面的指令可以弹出并使用该值。在编译时期就可以确定操作数栈的最大深度从而在执行的时候正确分配内存大小。
帧数据动态链接、方法出口、异常表的引用。动态链接用于保存符号引用到运行时常量池内存地址的一个映射关系。方法出口存放的是栈帧中下一个指令的执行地址。异常表存放的是代码中异常的处理信息包括异常捕获的生效范围以及发生异常后跳转到字节码指令的位置。
要修改 Java虚拟机栈的大小可以使用虚拟机参数-Xss例如-Xss256k
虚拟机HotSpot对栈的最大值与最小值有要求例如windows下jdk8的运行虚拟机栈的范围在180k-1024m之间。另外局部变量过多或者操作数栈深度过大也可能影响栈内存的大小。
本地方法栈(线程不共享)和Java虚拟机栈类似用于服务native方法。Java虚拟机栈存储的是Java方法调用时的栈帧而本地方法栈存储的是native本地方法的栈帧。在HotSpot虚拟机中Java虚拟机栈和本地方法栈使用同一块内存空间。
堆(线程共享)创建出来的对象存在堆内存中堆内存的大小是有上限的当一直向堆中放入对象达到上限之后就会抛出OutOfMemory(OOM)的错误。要修改虚拟机堆的大小可以使用参数
-Xmx值(max最大值) -Xms值(初始的total) 建议将这两个值设置为相同大小的这样程序在启动的时候使用的总的内存就是最大的内存无需向Java虚拟机再次申请减少堆内存申请的开销。
方法区(线程共享)主要存放三部分的信息1.类的基本信息2.运行时常量池字节码文件的常量池内容3.字符串常量池内容。在类加载阶段方法区会存放类的基本信息。JDK7及之前的版本将方法区放在堆内存中的永久代中堆的大小由虚拟机控制JDK8及之后的版本将方法区放到了元空间中元空间位于操作系统维护的直接内存中默认只要不超过操作系统承受的上限则可以一直分配。运行时常量池主要用于存储编译期生成的各种字面量和符号引用。字符串常量池主要用于存储字符串字面量的对象引用地址的一个区域。
注意String类型的字符串如果是变量拼接则转换成StringBuilder如果是常量拼接则直接拼接。
直接内存JDK8及其以后方法区就存在直接内存的元空间中。 四、JVM的垃圾回收机制
4.1、自动垃圾回收
内存泄漏指的是不再使用的对象在系统中未被回收内存泄漏的累积可能会导致内存溢出。
在C/C这类语言中没有自动垃圾回收机制一个对象不再使用需要手动释放否则会导致内存泄漏 。
Java为了简化对象的释放引入了自动垃圾回收机制通过垃圾回收器来对不再使用的对象进行回收垃圾回收器主要是对堆上的内存进行回收。 4.2、方法区的回收
我们知道类的声明周期包括加载连接(验证准备解析)初始化使用卸载五个部分其中最后一步的卸载就是方法区的回收。
判断一个类是否可以被卸载需要同时满足以下三个条件
1.此类的所有实例对象都已经被回收在堆中不存在该类的任何实例对象以及子类对象。
2.加载该类的类加载器已经被回收。
3.该类对应的java.lang.Class对象在任何地方都没有被引用。
如果需要手动回收垃圾需要使用 System.gc()方法 4.3、堆内存的回收
1.常见的垃圾回收算法
引用计数法与可达性分析法
引用计数法为每个对象维护一个引用计数器当对象被应用则加1取消引用则减1。该方法优点是十分简单。缺点存在循环引用的问题A引用B且B也引用A假如应用程序不再需要这两个对象但是 引用 计数器不为0所以导致这两个对象无法回收会出现内存泄漏。 可达性分析法Java使用的是可达性分析算法来判断对象是否可以回收可达性分析将对象分为两类垃圾回收的根对象(GC Root)与普通对象对象与对象之间存在引用关系。如果从普通对象到根对象的引用链是不可达的则说明该对象则是可回收的。
那么哪些对象可以被称为是GCRoot对象呢以下四类可以被称为垃圾回收根对象 1线程Thread对象
2系统类加载器加载的 java.lang.Class对象
3监视器对象用来保存同步锁synchronized关键字持有的对象
4本地方法调用时使用的全局对象
标记清除法在可达性分析算法确定了哪些对象是可达存活之后垃圾收集器将对那些不可达的对象进行标记操作。标记完成后垃圾收集器会清除所有被标记为不可达的对象占用的内存空间。优点实现简单第一阶段进行标记第二阶段进行清除即可。缺点内存碎片化清除掉一些碎片的内存可能导致无法再分配空间,维护链表进行分配分配速度慢。
标记复制法当前存活的对象复制到另一块空间将当前的空间进行清理掉。优点吞吐量高复制后不会发生碎片化。缺点内存空间的使用率低 。
标记压缩法也称为标记整理算法使用可达性分析标记存活的对象将存活的对象移动到内存的一侧清理掉存活对象之前的内存空间。优点内存使用率高不会产生碎片化。缺点整理算法的效率不高。
分代GC算法分代垃圾回收将整个内存区域划分成年轻代与老年代。
年轻代该区域包含三个部分伊甸园区Eden幸存者区S0幸存者区S1。
创建出来的对象首先会放入伊甸园区如果Eden区满了就会触发young GC如果可以被回收则回收否则放入幸存者区如果年轻代的对象一直没回收存活时间比较长的对象会被放入到老年代。当老年代不足的时候且年轻代也满了无法放入新的对象就会触发 full GC。 2.常见的垃圾回收器
为什么分代GC算法要把堆内存空间 划分成年轻代与老年代
因为年轻代用于存放可以很快被回收的对象比如用户订单数据。老年代则存放长期存活的对象比如 Spring的大部分Bean对象。在虚拟机的默认设置中新生代的大小 要远小于老年代的大小。总的来说分代GC算法将堆分成新生代和老年代的主要原因可以分为如下三点
1.可以通过调整年轻代和老年代的比例来适应不同类型的应用程序提高内存的利用率和性能。
2.新生代和老年代选择不同的垃圾回收算法新生代一般选择标记复制法老年代一般选择标记清除法与标记压缩法由程序员来选择灵活度较高。
3.分代的设计只允许回收新生代如果能满足对象分配的要求就不需要对整个堆进行回收即不需要full GC。
常见的垃圾回收器Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old
Serial是一种单线程串行的年轻代垃圾回收器使用的是标记复制法适用于单cpu进行处理垃圾回收不适用于多cpu下的处理多cpu下可能会使得其它用户线程处于长时间的等待状态。
SerialOld是Serial的老年代版本采用单线程串行回收可以与Serial垃圾回收器搭配使用。
ParNew是年轻代的垃圾回收器本质上是堆Serial进行多cpu下的优化使用多线程进行垃圾回收与老年代垃圾回收器CMS进行搭配使用。
CMS垃圾回收器关注的是系统的暂停时间允许用户线程和垃圾回收线程在某些步骤中同时进行减少了用户线程的等待时间。采用标记清除法垃圾回收停顿的时间短用户体验好但是存在内存碎片化如果老年代内存不足会退化成单线程SerialOld回收。CMS适用于请求数据量大频率高的场景例如订单接口、商品接口等。
Parallel Scavenge垃圾回收器是JDK8默认的年轻代垃圾回收器多线程并行回收关注的是系统吞吐量具备自动调节堆内存大小的特点。吞吐量高但是不能保证单次的停顿时间适用于后台任务比如大数据的处理、大文件的导出等。
Parallel Old垃圾回收器是为Parallel Scavenge设计的老年代的垃圾回收器利用多线程并发收集一般来说使用Parallel OldParallel Scavenge组合不需要设置堆内存的最大值垃圾回收器会根据最大暂停时间和吞吐量自动调整堆内存的大小。
当发生STW时JVM会暂停所有非垃圾收集线程即除GC线程之外的所有用户线程STW事件会导致应用程序暂时失去响应对于实时性要求高的应用来说STW时间过长可能会对系统性能造成显著影响。
G1垃圾回收器是年轻代和老年代的垃圾回收器JDK9开始之后默认的垃圾回收器G1垃圾回收器是结合了CMS垃圾回收器与Parallel Scavenge垃圾回收器的优点具备以下三个优点1.支持大的堆空间的回收并有较高的吞吐量。2.支持多CPU并行垃圾回收。3.允许用户设置最大暂停时间。
G1的整个堆会被划分成多个大小相等的区域区域不要求连续具体分为伊甸园区、幸存者区、老年代区。G1的垃圾回收有两种方式年轻代回收(young GC)回收伊甸园区和幸存者区不用的对象年轻代不足会触发young GC使用标记复制法进行垃圾清理。
当某个对象被复制多次存活年龄达到阈值15该对象将会被移入老年代多次回收后会出现很多的老年代区此时总堆的占有率达到阈值此时会采用标记复制法回收年轻代与老年代的对象。G1老年代的清理会选择存活度最低的区域进行回收可以保证回收效率最高。如果发现没有足够的区域存放转移的对象会触发full GC单线程执行标记整理算法会导致用户线程的暂停。 3.Java中四种引用方式 强引用通过可达性分析判断对象被引用则不是垃圾若对象到GCRoot不可达可以认为是垃圾会被回收掉。当内存不足的时候只有对象被引用就不会被回收。
软引用当程序内存不足的时候软引用对象会被回收掉。SoftReference类来实现软引用。软引用对象回收后内存仍然不足可能会报OOM。
弱引用使用弱引用的对象再使用完成后无论是否存在引用都会被回收掉。弱引用使用WeakReference类来实现弱引用主要在ThreadLocal中使用。
虚引用虚引用的作用是对象被垃圾回收器回收的时候可以收到相应的通知。