JVM介绍
JVM虚拟机介绍:
1.hotspot 2.jrockit 3.j9(IBM) 4.graal vm(oracle新一代虚拟机:https://github.com/oracle/graal)
jvm能运行的文件不单单是java前端编译而成的字节码文件,还能运行如Groovy,Scala,Koltin语言编译而成的字节码文件
JVM hotspot官网地址:
https://docs.oracle.com/en/java/javase/index.html




jvm可以设置的参数指定(jdk11和jdk8)官网地址:
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC
java和jvm各版本规范见官网地址:
https://docs.oracle.com/javase/specs/index.html
https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
HotSpot的JVM整体结构
类加载子系统+运行时数据区+执行引擎
类加载子系统
(类加载器(引导类加载器(bootstrap class loader(C语言实现的):加载类对象是jdk/jre/lib目录下的rt.jar和-xbootclasspath里面标注)和自定义类记载器(ext class loader(java实现):加载类对象是jdk/jre/lib/ext目录下的jar包和app class loader(java实现):如果没有自定义类加载器,除了需要被bootstrap和ext加载的类对象,其他都用app加载器加载)))
运行时数据区
(虚拟机栈,本地方法栈,程序计数器,堆,方法区),其中方法区的实现在jdk8以前叫永久代,jdk8及以后叫元空间.
虚拟机栈(一个方法一个栈帧组成,栈帧由(操作数栈,局部变量表,方法返回地址,动态链接,附加信息)组成)
存放方法内的局部变量,基本数据类型数值(double和long在局部变量表里面占两位,其他都是一位),引用类型地址
堆(年轻代和老年代组成,其中年轻代由eden和surrivor区组成;老年代和年轻代默认比例2:1(-XX:NewRatio=2),Eden和Surrivor比例8:1:1(-XX:NewSurrivor=4))
-Xms:256M(默认是系统内存的1/64之一)
-Xmx:256M(默认是系统内存的1/4之一)
存放对象,jdk7及以后有存放常量池,类信息
方法区(jdk8以前,永久代(PermGen),jdk8以后,元空间(metaspace))
jdk7以前存放常量池,类信息
线程独享区域(虚拟机栈,本地方法栈,程序技术器)
线程共享区域(堆,方法区)
最新版本jdk19以发布(2022-10月)
执行引擎(解释器和JIT编译器(特点:加载热点代码,预热耗时,执行很快);垃圾回收器).java语言特性:自动的内存空间分配(空间规整使用指针碰撞法;空间不规整使用空闲列表)和回收.
垃圾收集器:七种经典的垃圾收集器(serial,parnew,parallel scanvenge;serial old,cms, parallel old;g1),标记算法(引用技术法(该算法没有垃圾收集器采用)和可达性算法(gc roots));清除算法(复制算法(年轻代垃圾收集器采用),标记清除算法(cms,g1),标记压缩算法(serial old和parallel old))
jdk8默认的垃圾收集器是parallel的组合,jdk9及以后默认的垃圾收集器是g1.jdk11出现了zgc
垃圾回收器的两个性能指标:吞吐量(业务线程时间/(业务线程时间+垃圾回收时间)和低延迟(每次垃圾回收造成的停顿时间)互斥
吞吐优先的垃圾回收器:parallel
低延迟优先的垃圾回收器:cms(jdk9废弃,jdk14已经移除),g1,zgc(jdk11推出,jdk15摘除实验阶段),Shenandoah GC(jdk12推出,jdk8,jdk11(jdk11.0.9及以后的版本))(启用命令: -XX:+UseShenandoahGC)
开启垃圾收集器的命令(-XX:+PrintGCDetails)
引用类型(strong,soft,weak,虚)
强引用:99% 用等于号表示
软(内存不足时回收)弱(发生gc就回收)虚(主要是用来跟踪垃圾回收的,垃圾回收可以发布通知)引用:继承reference类,都是public类,还有一个FinalReference是默认的
代码实现方式以软引用为例:SoftReference obj = new SoftReference(new Object());
对象的状态:可触及的,可复活的,不可触及的.
每个jdk版本,垃圾回收都会有不同的新优化.
JVM结构说明:
类加载子系统(加载(loading)linking(校验+准备+解析) initialization初始化)
运行时数据区(方法区 堆 栈 程序技术器 本地方法栈)
线程独享区域(虚拟机栈 PC寄存器 本地方法栈)
线程共享区域(方法区 堆)
执行引擎(Interpreter JIT Compiler Garbage Collector)(基于栈的解析)
加载
(bootstrap(jdk lib/rt.jar -xbootclasspath标注的) extention(lib/ext/目录下的jar包) application(其他所有类))
类加载器(引导类加载器和自定义类加载器)
引导类加载器:bootstrap(由C语言实现)
自定义类加载器:除了bootstrap其他都是
栈结构
(栈帧:一个方法就是一个栈帧,栈只有入栈和出栈两种操作):
局部变量表(栈结构,存储临时变量的值或对象的引用地址)
操作数栈(栈结构,给执行引擎操作使用)
动态链接(将方法引用变为直接引用;动态绑定和静态绑定;早期绑定和晚期绑定;虚方法和非虚方法;提高效率(虚方法表))
方法出口(返回下一个指令地址,有ireturn,freturn,dreturn,lreturn,return)
附加信息
堆
年轻代(eden s1 s2(8:1:1))1 默认gc收集15次满了之后放入老年代,初始化对象较大直接放入老年代
老年代 2
永久代(jdk8以前) 元空间(jdk8以后)
堆空间分为:新生代(Eden,s0,s1),老年代,方法区(jdk1.8以前叫永久代,1.8及1.8以后叫元空间)
新生代和老年代比例默认1:2(设置操作指令:-XX:NewRatio=2)
Eden区,s0,s1比例默认8:1:1(设置操作指令:-XX:SurvivorRatio=4)
-Xms256M(默认是系统内存的1/64之一)
-Xmx256M(默认是系统内存的1/4之一)
优化一般将初始内存和最大内存设置为一样
打印gc详细说明:
-XX:+PrintGCDetails(默认是关闭的)
gc回收的主要区域:
eden区满了,发生minor gc,进行新生代的垃圾收集,STW较短
老年代满了,发生major gc或full gc 进行老年代空间或者整堆和方法区的垃圾收集,STW较长,如果内存空间还是不足抛出OOM
方法区(永久代或元空间满了),进行full gc
本地方法栈
java 底层c++,c语言实现的,本地方法栈(调用c++程序 .dll文件)
调试工具:
jclasslib: idea插件,查看字节码文件的指令
javap -v -p xxx.class >xxx.txt :java指令,查看字节码文件的指令
VisualVM
jconsole
jprofile
JVM指令设置官网说明地址: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
jvm调优:
减少fgc执行次数和时间
STW(stop the world)
jvm性能调优工具
jdk下面的自带的工具指令
jps 查询java程序进程号
jinfo -flags <进程号> 显示jvm设置的一些参数 内存块设置大小,gc种类
jstat -gc <进程号> 堆内存的使用情况,垃圾回收的一些情况
-XX:heapDumpPath=路径
oomdump.dump文件导入VisuaJVM分析
jstack 9360 > deadLock.txt 打开分析
导出项目gc日志
默认元空间水平线达到21M会进行垃圾回收,回收不了上调10M
gc机制:
引用计数法
可达性算法
gc算法:
复制 (效率较高,开辟两片相等空间)S1 S2
标记清除(效率问题,标记和清除两个过程效率都不是很高,空间问题 会产生大量的碎片空间)EDEN
标记整理(效率还是比较慢)old
分代收集()
概念:
并行:垃圾线程并行工作
并发:垃圾线程和用户线程并发工作
垃圾收集器:
serial收集器:新生代采用复制,老年代采用标记-整理(单线程 STW)
ParNew收集器:新生代采用复制,老年代采用标记-整理(多线程 STW)
Parallel Scanvenge:新生代采用复制,老年代采用标记-整理(多线程 STW)
CMS 收集器:
阶段:
初始标记:记录和gc root相连的对象
并发标记
重新标记
并发清除
缺点:
对cpu资源比较敏感
无法处理浮动垃圾
标记-清除容易产生碎片空间
G1收集器:
区域:
Region
发生gc:
mixgc
阶段:
初始标记
并发标记
最终标记
筛选回收:
维护一个列表的优先集,先回收优先集高的
对服务器硬件要求较高
GC调优:
两个指标:
1.优化停顿时间
2.优化吞吐量 吞吐量(为1-总时间/总时间+垃圾回收时间) 并发数/平均响应时间
输出gc日志
gceasy分析日志,看日志分析。
JVM详解
JVM运行时数据区可以分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器,oracle的JVM为HotSpot。
除了运行时数据区,JVM里面还包括执行引擎和本地库接口。
执行引擎包括解释器、JIT(即时编译器)、GC(垃圾回收器)。本地库接口会提供java程序调用的native方法。

但随着jdk各版本的发布,运行时数据区的结构会各有差异,可参见下图:

虚拟机栈:
jvm启动后默认的虚拟机栈大小为1M,当遇到需要拷贝List集合数据较多时,可能会发生stackOverFlowError的异常,使用-Xss100m指令调大栈内存即可.
每个java线程都会对应一个虚拟机栈,多个线程就会对应多个虚拟机栈。虚拟机栈里面又包含多个栈帧,每一个栈帧是为方法执行而创建的,栈帧中描述的是java方法执行的内存模型。
栈帧中包含局部变量表、操作数栈、动态链接、返回地址。

局部变量表:
它定义为数字数组,主要用于存储方法参数和定义在方法内的局部变量。局部变量表所需的容量在编译期间确定,在运行期间是不改变其容量。
操作数栈:
它是一个后进先出的栈,根据字节码指令,往栈中写入或取出数据。操作包括:复制、交换、求和等










动态链接:
解释:被调用的方法在编译期间无法被确定下来,只能在程序运行时将调用方法的符号引用转换为直接引用,由于这种引用转换的过程具备动态性,被称为动态链接。
静态链接:在字节码文件被装载到JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。

栈帧中的当前常量池引用保存的是方法符号引用,真正的方法引用放在了方法区中的方法引用中了。

JVM之所以这样设计是因为字节码文件需要数据支持的量会很大,因此不能直接将这些数据存放到字节码中。针对方法的引用创建符号引用,这个符号引用放在栈帧的常量池引用中,而实际的方法和符号引用的对照表却放在方法区的常量池中,这样字节码就可以通过常量池中的对照关系找到引用方法,并且也不会增加栈帧的容量。
符号引用与直接引用:
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可,使用符号引用时,被引用的目标不一定已经加载到内存中。
直接引用可以直接指向目标的指针,相对偏移量,一个能间接定位到目标的句柄,使用直接引用时,引用的目标必定已经存在于虚拟机的内存中了。
方法返回地址:
方法在返回的时候需要在栈帧中保存一些信息,用来恢复调用该方法的上层方法的执行状态。
程序计数器:
程序计数器就是一块较小的内存空间,它是当前线程执行字节码的行号指示器。每个栈帧都会维护一个属于自己的程序计数器,这个计数器就是用来记录执行的地址的。

本地方法栈:
本地方法栈和虚拟机栈类所发挥的作用非常类似。它们之间的区别就是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机所使用到的native方法服务。本地方法只有被调用之前,DLL才会被加载,即通过调用java.system.loadLibrary()实现的。
堆和方法区(线程共享区域)
堆:
Java堆是java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享。Java对象实例以及数组都在堆上分配。Java堆也是垃圾回收器的主要工作区域。java推细分还可以分为新生代和老年代。新生代又可以分为Eden、From Survivor、To Survivor。
方法区:
它用来存放虚拟机加载的类型信息、运行时常量池、静态变量、JIT代码缓存、域信息、方法信息等。
总结下特点:
1.方法区在JVM启动时被创建,并且它的实际的物理内存空间和Java堆一样都可以是不连续的
2.方法区的大小,和堆空间一样,可以选择固定大小和可扩展
3.方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机栈就会抛出内存溢出错误:
OutOfMemoryError:PermGenspace或MetaSpace
4.关闭JVM就会释放掉这个区域的内存

参考博客:
https://developer.51cto.com/article/704419.html
