JVM调优

类加载

装载:将class文件转化为二进制文件,并将类描述信息放入方法区,将类对象存入堆

链接:验证文件是否正确后,将静态成员放入方法区并为静态变量赋初始值,将类的符号引用转换为直接引用

初始化:将静态变量赋值,并运行静态代码块

类加载器

启动类加载器:加载核心类库;

拓展类加载器:加载拓展类库;

应用类加载器:加载自己写的;

双亲委派模型:父类加载器先处理

打破双亲委派模型:自定义类加载器,继承继承 ClassLoader 类,重写 loadClass 方法(tomcat就是这样干的)

JVM内存模型

线程独享区:线程销毁后数据就被回收

线程共享区:线程销毁后数据不会立即回收,达到GC的阈值后回收

程序计数器:存放当前线程执行语句的地址

虚拟机栈:存放当前线程声明的变量

基本数据类型存值,引用数据类型存地址,都占4个字节

本地方法栈:存储非JAVA语言执行产生的数据

虚拟机栈

栈帧:执行到方法将方法压栈,完成后弹栈

        每一个线程都会对应一个虚拟机栈,线程中的每个方法都会创建一个栈帧,存放本次方法执
行过程中所需要的所有数据。
        如果我们一个线程中有多个方法的嵌套调用,虚拟机栈会对栈帧进行压栈和出栈操作。正在
执行的方法一定在栈顶,我们只能获取栈顶的栈帧,栈帧在虚拟机栈中先进后出。
栈帧结构:

 常量数据存放在常量池中,常量池在方法区中

栈溢出:栈内存常为256K~1M

栈内存调优指令

-Xss256k //内存大小

栈内存太小容易溢出,栈内存太大影响线程数量

方法区(元空间)

存储类信息,静态变量,常量(jdk8后不存储字符串常量)

JVM 执行引擎

        执行引擎是 Java 虚拟机核心的组成部分之一。JVM 的将字节码装载到内存,但字节码并不能够直接运行在操作系统之上。为了执行内存中的字节码文件指令,执行引擎(Execution Engine)就要将字节码指令 解释/编译为对应平台上的 本地机器 指令。
        执行引擎的翻译过程有两种:1、通过解释器将字节码文件转为机器指令执行;2、使11用即时编译器(JIT)将字节码文件的二进制流编译成机器指令执行。
        目前市面的主流 JVM 采用解释器与即时编译器并存的架构。在 Java 虚拟机运行时, 释器 即时编译器相互协作。
         解释器每次解释都会将字节码文件解释为机器指令。整体效率较低,但当程序启动后,解释器可以马上发挥作用,省去编译的时间,立即执行。
        即时编译器则会将字节码文件编译为机器指令,存在方法区中,编译完成后直接执行本地机器指令即可。
        当 Java 虚拟器启动时,解释器首先发挥作用,不必等待即时编译器全部编译完成后再执行。

堆内存

对象头

MarkWord:一系列标记位(哈希码、分代年龄、锁状态标记等),在 64 位系统中占 8 字节。
ClassPoint:对象对应的类信息的内存地址,在 64 位系统中占 8 字节。
Length:数组对象特有,表示数组长度,占 4 字节。
实例数据:包含了对象的所有成员变量
对其填充:将类内存填充为8字节的整数倍
内存划分

 老年代:

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

新生代:

        为了减少young区垃圾回收后的空间碎片,新生代又分为Eden区和两个Survivor 区,且始终有一个 Suvivor 区保持闲置。对象会先存放到 Eden 区当中,Eden 区空间满了之后会进行 young区的垃圾回收,之后将 young 区所有存活的对象复制到闲置的 Suvivor 区中,并清空 Eden 区和正在使用的 Survivor 区。

老年代垃圾回收 

浪费时间:老年代没有分区,垃圾回收后会整理碎片。JVM调优要尽可能减少oldGC次数。oldGC往往伴随着youngGC,oldGC + youngGC = fullGC。

新生代垃圾回收

新生代区域的垃圾回收称之为 YoungGC,也叫 MinorGC,Eden 区满后会触发 YoungGC

设置堆内存指令

-Xms10M(新生代) -Xmx10M(老年代)

面试3问:

1. Survivor 区空间并不大,如果满了怎么办?
        一般情况下 GC 会回收 95%的对象,且超过 15 次 GC 的对象会存放到 old 区,所以 Survivor 区不容易满。
        如果 Survivor 区满了,会触发担保机制,提前将对象存入 Old 区。
2. 为什么需要 Survivor 区?
        为了减少垃圾回收带来的空间碎片,空间碎片过多会频繁触发 YoungGC。
3. 为什么需要两块 Survivor 区?
        为了减少 Survivor 区的空间碎片。

垃圾回收

可达性分析

引用计数法

        如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到。那么这个对象就成为可被回收的对象了。这种方式实现简单,效率较高,但是它无法解决循环引用的问题(python采用)

可达性分析

        以一个 G C Root 对象作为起点进行搜索,如果在 GC Roots 和对象之间没有可达路径,则称该对象是不可达的

GC Root 对象

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

垃圾回收算法

标记——清除算法

        效率较低,要扫描两次堆内存,第一次标记不可达对象,第二次清除标记的对象,有空间碎片。old 区使用的算法

复制算法

空间碎片少,但会浪费空间。存活对象较少才会使用的算法。young 区使用的算法

标记——整理算法

空间碎片少,效率较低。old 区使用的算法

垃圾收集器

垃圾收集器的执行效率 = 吞吐量 / 停顿时间
吞吐量 = 用户代码执行时间 / (用户代码执行时间 + 停顿时间)

串行收集器

只有一个垃圾回收线程,在垃圾回收时暂停用户代码线程,如 Serial 和 Serial Old 收集器

并行收集器

        吞吐量优先,多个垃圾收集器线程共同工作, 尽快完成垃圾收集。如 ParNew,Parallel Scanvenge, Parallel Old 收集器

并发收集器

停顿时间优先,用户线程和垃圾回收线程一同工作,用户代码线程也会完全停止一小段时间,如 CMS,G1 收集器

CMS 收集器 

        CMS(concurrent mark sweep,并发标记扫描)收集器是并发收集器,是基于标记——清理的算法进行垃圾回收,用于 OldGC。
优点:并发收集、低停顿
缺点:会产生大量空间碎片,停顿时间虽然短但是不可控

G1 收集器

        G1(garbage first,垃圾优先)收集器是并发收集器,从 JDK1.7 开始支持,能进行 oldGC 和 YoungGC。Old 区采用标记整理算法,Young 区采用复制算法。
        G1 收集器没有固定的 Old、Young、Eden、Survivor 区,而是将内存分为一个个大小相等的 Region(格子,1Mb~32Mb)。每次垃圾回收后,Region 的用途可以发生改变,提高了内存的灵活性和利用率
        G1 收集器可以根据开发者设置的参数,停顿时间的期望值,优先筛选回收存活的对象比较少,垃圾对象比较大的区域 Region,可以把更多空余的空间释放出来

ZGC 收集器

        ZGC 从 JDK11 开始支持,目前还是一个实验性版本,原理类似 G1。是目前收集效率最高的垃圾收集器,平均暂停时间为 0.05 毫秒
  • 优先让服务器自己来选择
  • 如果内存小于 100M,使用串行收集器
  • 如果服务器是单核,并且没有停顿时间要求,使用串行收集器
  • 如果允许停顿时间超过 1 秒,选择并行收集器
  • 如果停顿时间不能超过 1 秒,使用并发收集器

JVM 参数设置

JVM 参数类型

标准参数

不随 jdk 版本的变化而变化的参数,如:-version

-X 参数

不能保证所有的 JVM 都支持。
如: -Xcomp:使用即时编译器执行字节码文件
        -Xint:使用解释器执行字节码文件
        -Xmixed:混合模式,先使用解释器,即时编译器编译好后执行机器指令。

-XX 参数

不能保证所有的 JVM 都支持
Boolean 类型参数: -XX:+UseG1GC:使用 G1 收集器 ;-XX:-UseG1GC:不使用 G1 收集器
Key-Value 类型参数: -XX:MaxTenuringThreshold=15:对象年龄达到 15 就会进入老年代

jinfo:实时查看 JVM 参数
jinfo -flag InitialHeapSize PID:JAVA 进程堆内存大小
jinfo -flag UseG1GC PID:JAVA 进程是否使用 G1GC
jinfo -flag UseParallelGC PID:JAVA 进程是否使用 ParallelGC
jstat:虚拟机性能信息
jstat -class PID 1000:每秒查看一次虚拟机中类加载信息
jmap:打印快照
jmap -heap PID:查看堆存储快照
jmap -dump:format=b,file=heap.hprof PID:出现内存溢出异常时,将堆内存信息下载到文件中
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof   //项目在发生 OOM 异常时自动下载堆内存信息
异常信息会生成在项目的根目录下
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:gc.log
打印GC相关信息,生成日志

GC 调优

不要手动设置新生代和老年代的大小,只设置堆的大小
不断调优暂停时间目标:一般情况设置到 100ms 或者 200ms 都是可以的,但如果设置成 50ms 就不太合理。暂停时间太短,会导致 GC 跟不上垃圾产生的速度
适当增加堆内存大小

高并发环境下配置堆和垃圾回收器

计算内存大小和高并发下的请求数,设置高并发时间内不要进行GC。将短时间高并发内将young区和old区内设置成能支撑的内存。

JVM 问题的排查

 

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值