导图社区 JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
编辑于2021-01-13 12:16:42走进JVM
.java
编译器
.class
类加载器
1. Java虚拟机自带的加载器
BootStrap
根加载器
JVM启动时创建 负责加载JAVA_HOME\lib目录中的,或通过-Xbootclasspath参数指定路径中的 且被虚拟机认可(按文件名识别,如rt.jar)的类,负责装载最核心的Java类 比如Object,System,String等
Extension
扩展类加载器
负责加载JAVA_HOME\lib\ext 目录中的 或通过java.ext.dirs系统变量指定路径中的类库,加载一些扩展的系统类 比如XML,加密,压缩相关的功能类等
Application
应用类加载器
主要加载用户定义的classpath路径下的类
2. 用户自己定义的加载器
继承java.lang.ClassLoader实现自定义的类加载器
3. 双亲委派模型
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成 每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中 只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class) 子类加载器才会尝试自己去加载
保护java的核心类不会被自己定义的类所替代
加载
通过一个类的全限定名(包名与类名)获取定义此类的二进制字节流,并转化为特定的数据结构 初步校验cafe babe魔法数、常量池、文件长度、是否有父类等 然后在JVM堆中创建一个代表这个类的java.lang.Class实例,作为方法区域数据的访问入口 注意这里不一定非得要从一个Class文件获取 既可以从ZIP包中读取(比如从jar包和war包中读取) 也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)
链接
验证
验证Class文件的字节流包含的信息是否符合JVM规范 比如final是否合规、类型是否正确、静态变量是否合理等 如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常
准备
静态(static)变量(不包括类的实例)分配内存,并设置变量的初始化,此初始化并不是赋值。 比如一个类变量定义为:public static int v = 8080; 实际上变量v在准备阶段过后的初始值为0而不是8080, 将v赋值为8080的putstatic指令是程序被编译后,存放于类构造器方法之中。 但是注意如果声明为:public static final int v = 8080; 在编译阶段会为v生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将v赋值为8080
解析
将常量池中的符号引用替换为直接引用的过程。 解析类和方法,确保类与类之间的相互引用正确性,完成内存结构布局
初始化
赋值阶段
执行类的构造器方法进行赋值,如果赋值运算是通过其他类的静态方法来完成的 那么会马上解析另外一个类,在虚拟机栈中执行完成后通过返回值进行赋值
类加载过程
本地方法栈
本地方法栈为native方法服务,底层调用的c或者c++等其他语言。 native关键字修饰,说明 java的作用范围达不到,只能去调用底层 C/C++ 语言的库
JNI
Java Native Interface (Java 本地方法接口)
最著名的本地方法例如System.currentTimeMillis()
程序计数器
程序计数器是一块很小的内存空间,每个线程都有一个程序计数器,是线程私有的,用来存放执行指令的偏移量和行号指示器等
方法区(Method Area)
1.7版永久代,主要存放 Class 和 Meta(元数据)信息, 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。 JDK8使用元空间替换永久代。 元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 因此,默认情况下,元空间的大小仅受本地内存限制。 类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中, 这样可以加载多少类的元数据就不再由 MaxPermSize控制, 而由系统的实际可用空间来控制。
虚拟机栈
栈是一个先进后出的数据结构
管理程序运行
每一个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程
栈帧
堆
新生代
绝大部分对象在Eden区创建(如果新创建的对象占用内存很大,则直接分配到老年代)
当Eden区满了之后会触发YGC(垃圾回收),垃圾回收的时候实现清除策略,没有被引用的对象会直接收回. 依然存活的对象会移送至S区中的S0或S1. 每次YGC的时候,会将存活对象复制到未使用的那块空间,然后将当前使用的空间完全清除,交换两块空间的使用状态。 如果YGC要移送的对象大于S区的容量上限时,会直接移送到老年代。 每个对象都有一个计数器,每次YGC都会+1,当计数器的值到达某个阀值(默认15)的时候,对象会从新生代晋升为老年代
老年代
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。 在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。 当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。 MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。 MajorGC的耗时比较长,因为要扫描再回收。 MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。 当老年代也满了装不下的 时候,就会抛出OOM(Out of Memory)异常
永久代
JDK1.8以后,叫元空间
元空间并不在虚拟机中,而是使用本地内存
OOM
1. java.lang.StackOverflowError
栈溢出
一般递归方法导致
2. java.lang.OutOfMemoryError: Java heap space
堆溢出
3. java.lang.OutOfMemoryError: GC overhead limit exceeded
GC 回收时间过长
4. java.lang.OutOfMemoryError: Direct buffer memory
基础缓冲区的错误
5. java.lang.OutOfMemoryError: unable to create native Thread
应用创建的线程超过了服务器限制,也会爆出OOM异常
6. java.lang.OutOfMemoryError: Metaspace
元空间溢出
Dump内存快照
查看程序的运行情况
1. JDK中Jconsole
2. idea debug
3. Eclipse(MAT插件)
4. IDEA(Jprofiler插件)
性能分析工具
GC
分代收集算法
不同的区域使用不同的算法
新生代
普通GC
只针对新生代 (GC)
老年代
全局GC
主要是针对老年代,偶尔伴随新生代(FullGC)
复制算法
按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉
优点:没有标记和清除的过程!效率高!没有内存碎片!
缺点:需要浪费双倍的空间
新生代采用此算法
标记清除算法
分为两个阶段,标记和清除。 标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间
优点:不需要额外的空间!
缺点:标记和清除前都需要扫描,两次扫描,耗时较为严重,会产生内存碎片,不连续!
标记整理算法
分为两个阶段,标记和整理。标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
优点:不需要额外的空间,不会产生内存碎片
缺点:标记和整理前都需要扫描,两次扫描,耗时较为严重
老年代
标记-清除-整理
引用计数法
java弃用,Python用
GC Roots
1. 虚拟机栈中引用的对象
2. 类中静态属性引用的对象
3. 常量引用的对象
4. 本地方法栈中 Native 方法引用的对象
扫描:从 每个GC Roots出发依次标记有引用关系的对象
引用
强引用
JVM不会回收的对象
软引用
如果系统内存充足,GC不会回收此对象,但是内存不足的情况,就会回收对象!
弱引用
不论内存是否充足,只要是GC,就会回收该对象
虚引用
虚无 ,没有这个引用
GC如何确认垃圾?
可达性分析算法
如果一个对象与GC Roots之间没有直接或者间接的引用关系,那就代表是可以回收的
垃圾收集器
串行垃圾回收器
单线程回收垃圾,其他用户线程Stop
并行垃圾回收器
多线程回收垃圾,其他用户线程Stop
Stop the World)
并发垃圾回收器
回收垃圾同时,可以正常执行线程,并行处理,但是如果是单核CPU,只能交替执行
G1垃圾回收器
将堆内存分割成不同的区域(默认2048块)然后并发的对其进行垃圾回收,提高效率
-XX:MaxGCPauseMillis=100 最大的GC停顿时间。单位:毫秒,JVM尽可能的保证停顿小于这个时间!
JVM参数类型
1. 标配参数
在各种版本之间都很稳定,很少有变化
例如:
java -version
java -help
java -showversion
2. JVM运行参数(-X)
例如:JVM执行三种模式
1. 解释执行
java -Xint -version
2. JIT编译执行
java -Xcomp -version
3. JIT编译与解释混合执行(主流JVM默认执行模式)
java -Xmixed -version
3. -XX: + 或者 - 某个属性值
+ 代表开启某个功能,- 表示关闭了某个功能
JVM参数配置
1. -XX:+TraceClassLoading
用于追踪类的加载信息并打印出来 // 分析项目启动为什么这么慢,快速定位自己的类有没有被加载!
2. -XX:+PrintGCDetails
输出详细的垃圾回收信息
3. -XX:+PrintFlagsInitial
查看 java 环境初始默认值
= 默认值
:= 就是被修改过的值
4. java -XX:+PrintCommandLineFlags -version
打印出用户手动选项的 XX 选项
5. -Xms1024m
设置初始堆容量
一般为物理内存的1/64
6. -Xmx1024m
设置最大堆容量
一般为物理内存的1/4
生产环境Xms和Xmx设置一样大小
7. -Xss128k
设置线程栈大小,默认 512k~1024k
8. -Xmn
设置新生代的大小
9. -XX:MetasapceSize=128m
设置元空间内存大小
10. -XX:SurvivorRatio
设置新生代中的 s0/s1 空间的比例
Eden:s0:s1 = 8:1:1
-XX:SurvivorRatio=8
Eden:s0:s1 = 4:1:1
-XX:SurvivorRatio=4
11. -XX:NewRatio
设置新生代与老年代的占比
新生代1,老年代是2,默认新生代整个堆的 1/3;
-XX:NewRatio=2
新生代1,老年代+是4,默认新生代整个堆的 1/5;
-XX:NewRatio=4
12. -XX:MaxTenuringThreshold=15
设置计数器阈值
13. java -XX:+PrintCommandLineFlags -version
查看默认的垃圾回收器
14. -XX:Use........
设置垃圾回收器类型
单CPU,单机程序,内存小
-XX:UseSerialGC
多CPU,大的吞吐量、后台计算!
XX:+UseParallelGC
多CPU,但是不希望有时间停顿,快速响应
子-XX:+UseParNewGC 或者 XX:+UseParallelGC
本地方法接口
本地方法库
执行引擎
运行时数据区(Runtime)
虚拟机栈
方法区
程序计数器
堆
本地方法栈