JVM 全面详解:深入理解 Java 的核心运行机制

基础:

Jvm就是java虚拟机,

java执行流程:

文本文件(java)->编译器->.class文件->jvm->虚拟指令->执行引擎->解释为机器指令执行

编译的本质是将java源文件转为jvm能够认识的16进制class文件

类加载机制:

类加载的过程:

将.class文件转化为二进制文件加载到内存中,并转为可以运行的java.lang.Class对象,供jvm使用

1.1装载

(1)通过类加载器获取类的全限定类名,将class文件转为二进制文件

(2)将二进制流中类的描述信息存入方法区,如:创建时间

(3)将Java.lang.Class对象存入堆中

1.2链接

(1)验证加载类的正确性

(2)在方法区为静态变量分配空间,设置默认0值

(3)把类的符号引用转为直接引用

1.3初始化

为类的静态变量赋值,执行静态代码块

常见类加载器

  • BootstrapClassLoader (启动类加载器):加载核心类库(rt.jarjava.base)。如:java.util.Date,java.lang.String

  • ExtensionClassLoader (扩展类加载器):加载 jre/lib/ext-Djava.ext.dirs 指定目录下的类库。

  • AppClassLoader (应用类加载器):加载 classpath 下的类。

双亲委派模型

类加载收到加载请求时,先由父类加载器去加载,父类加载器无法加载时,再由自己加载

BootstrapClassLoader<-ExtensionClassLoader<-AppClassLoader 依次 继承

面试题:如何打破双亲委派机制?

自定义类加载器类,继承ClassLoader类,重写loaderClass方法

内存模型

什么是jvm内存模型 每启动一个java程序,jvm会在进程中划分一块内存区,jvm将内存区划分为5块

jvm内存划分

jvm按照线程是否共享将内存首先分成两大类

线程独享区

只有当前线程能访问数据的区域,线程之间不能共享 线程独享区随线程的创建而创建,随线程的销毁而回收

线程共享区

所有线程都可以访问的区域, 当线程被销毁的时候,共享区的数据不会立即回收,需要等待达到垃圾回收的阈值之后才进行回收

程序计数器

记录当前线程要执行指令的内存地址,为了多线程切换时,能恢复到正确执行位置 只记录一个地址,不会出现内存溢出的问题

虚拟机栈

存当前线程中声明的变量,包括基本数据类型的数据和引用数据类型的引用

基本数据类型:能够确认占用内存的大小

引用数据类型:不能够确认占用内存的大小

将值的引用存放到虚拟机栈中,而对象存放在堆内存中

栈帧

每一个线程会创建一个虚拟机栈,线程中的每个方法都会在虚拟机栈中创建一个栈帧,存放本次方法执行过程中所需要的所有数据,先进后出

虚拟机栈溢出及调优

栈溢出99%是因为递归,可以通过修改内存大小设置栈帧的最大深度

本地方法栈(了解,并非四大分区)

存储的是维护非java程序的执行过程中产生的数据

方法区(java8之后叫元空间)

方法区会存储类信息,静态变量,常量,机器指令

如果加载大量class文件,也会造成方法区内存溢出

jvm执行引擎

将字节码指令解释/编译为对应平台上的本地机器指令

解释器:将字节码文件解释为机器指令

即时编译器:会将字节码文件编译为机器指令,存在方法区中,编译完成后直接执行本地机器指令即可

混合模式:

解释器逐条执行字节码,记录热点方法 热点方法交给编译器编译成机器指令

堆内存模型

jvm将对象存放在堆内存,堆内存空间是比较大的。所以我们对于jvm调优,也是主要针对于堆内存调优,比如分配堆内存的空间

对象内存布局

对象头+实例数据+对齐填充()

jvm内存溢出(OOM)和垃圾回收(GC)机制

如果对象只创建不回收,则会造成内存溢出(OOM)

为什么进行堆内存划分?

1.垃圾回收后可以更好的利用内存空间,存放大对象 2.提高搜索垃圾的效率 3.尽可能减少GC次数

堆内存的划分

新生区:占1/3

又分为Eden区和两个Survivor区,且始终有一个Survivor区闲置。对象创建后会先放入Eden区,当Eden区满了之后会进行GC,之后将存活下来的对象复制到闲置的Survivor区,并清空Eden和正在使用的Survivor区。

老年区:占2/3

对象优先分配到新生区内存,每次GC没有回收的对象年龄+1,年龄到15还没有回收,则存放到老年区内存;如果对象较大,超过新生区内存的一半,对象也会放到老年区

YongGC和OldGC

OldGC:之后会进行补齐十分影响性能,尽可能减少

Survivor区空间并不大,满了怎么办?

会触发担保机制,提前将对象存入Old区

为什么需要Survivor区?

为了减少垃圾回收带来的空间碎片,空间碎片过多会频繁触发YongGC

为什么需要两块Survivor区?

为了减少Survivor区的kong

使用VisualVM监听java进程的内存模型

垃圾回收

如何判断垃圾?

引用计数法:

当有一个地方引用它时,计数 +1;引用失效时,计数 -1。

当计数为 0 时,说明对象不再被使用 → 可回收。

会出现循环引用的问题

可达性分析:

从一组 GC Roots 对象出发,沿着对象引用链不断向下搜索。

如果某个对象不能通过任何引用链连接到 GC Roots,就判定为"不可达对象",就是垃圾。

GC Root对象:

栈帧中引用的对象 方法区中静态属性引用的对象 方法区中常量引用的对象 本地方法栈中引用的对象

回收算法

1.标记-清除

标记所有可达对象,然后清除未标记的,内存碎片化严重

2.复制(新生代)

将内存分为两块,每次只使用一块,当满了时,将存活的对象复制到另一块,清除原有区域

3.标记-整理(老年代)

标记所有可达对象,然后清除未标记的,让存活对象移向一端,减少内存碎片

为什么新生代用复制算法,老年代用标记-整理?

新生代对象存活率低,复制成本小,老年代对象存活率高,复制成本太大,所以用标记-整理

垃圾收集器

CMS收集器

CMS是并发收集器,是基于标记-清除算法进行垃圾回收的,用于OldGC

并发收集,低停顿,但是有内存碎片,停顿时间不可控

G1收集器

并发收集器,能支持YongGC和OldGC

G1没有固定的Old,Yong,Eden,Survivor区,而是将内存分为大小相等的Region,每次回收后,Region的用途可以改变

可以根据开发者设置的参数,停顿时间的期望值,优先回收垃圾最多的Region

" CMS 和 G1 有什么区别?" 你可以这样答:

CMS 是一种 低延迟 的并发收集器 ,采用标记-清除算法,适合中小堆内存、对响应时间敏感的应用,但存在内存碎片和浮动垃圾问题。 G1 是 JDK9 默认的收集器,采用 分区化的标记-整理算法,可以避免碎片,并且能根据用户设定的停顿时间优先回收垃圾最多的 Region,更适合大堆内存和对延迟可控要求高的场景。

jvm调优

参数类型

-X参数 -XX参数

1.防止OOM

2.垃圾回收调优

2.1提高吞吐量 2.2减少停顿时间

堆内存文件查看工具

给jvm配置以下代码,让项目发生OOM时自动下载堆内存信息

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof

PerfMa查看生成的文件,可以看到各类对象实例数,实例的大小,使用的类加载器

GC日志查看工具

给jvm配置以下代码,将GC信息打印出来到gc.log文件

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:gc.log

使用GCViewer查看log文件的GC信息,可以看到吞吐量,平均停顿时间,GC次数

jvm监控工具

jvisualVM

能查看参数命令,堆,cpu,类的使用情况

G1GC调优

经过实践,Parallel,CMS,G1,得出G1的吞吐量大,平均停顿时间少,GC次数多

1.适当增加堆内存 2.设置停顿时间 ;设置停顿时间:-XX:MaxGCPauseMillis=

常见面试题补充

内存泄漏和内存溢出是一样的概念吗?

不一样,内存泄漏是指不再使用的对象无法被回收,一直占用内存空间 内存溢出是指程序运行的最大内存大于能提供的最大内存

方法区中的类会被回收吗?

有可能,1.该类的所有实例都被回收,2.该类的类加载器被回收了

CMS 和 G1 的区别

1.cms 只能适用于老年代,g1 可以用于老年代和新生代。

2.cms 使用了标记------清除算法,会产生大量碎片,g1 使用标记------整理算法,减 少了碎片的产生。

3.g1 更加灵活,它将内存分为了一块块 region,并且停顿时间可控。

相关推荐
上官浩仁2 小时前
springboot excel 表格入门与实战
java·spring boot·excel
Hello.Reader3 小时前
从零到一上手 Protocol Buffers用 C# 打造可演进的通讯录
java·linux·c#
树码小子3 小时前
Java网络初识(4):网络数据通信的基本流程 -- 封装
java·网络
稻草人想看远方3 小时前
GC垃圾回收
java·开发语言·jvm
en-route4 小时前
如何在 Spring Boot 中指定不同的配置文件?
java·spring boot·后端
百锦再4 小时前
在 CentOS 系统上实现定时执行 Python 邮件发送任务
java·linux·开发语言·人工智能·python·centos·pygame
echoyu.4 小时前
消息队列-kafka完结
java·分布式·kafka
七夜zippoe4 小时前
分布式事务性能优化:从故障现场到方案落地的实战手记(二)
java·分布式·性能优化
栀椩4 小时前
springboot配置请求日志
java·spring boot·后端