【JVM】优化-基础知识

一、jvm基础

1、JDK JRE JVM

jdk = jre + development kit
jre = jvm + core lib
补充:development kit
英 [dɪˈveləpmənt kɪt] 美 [dɪˈveləpmənt kɪt]
开发工具包;开发包;开发套件;开发工具;软件开发包

在这里插入图片描述

2、Class File Format

1)class文件查看工具
IDEA中的binED插件,可以查看2、8、16进制查看
jclasslib工具可以对class文件每个字节进行编译解释
2)class文件的数据结构
minor
英 [ˈmaɪnə®] 美 [ˈmaɪnər]
adj.少数的;轻微的;较小的;次要的;小调的;小音阶的
n.未成年人;辅修科目;辅修课程 v.辅修

major
英 [ˈmeɪdʒə®] 美 [ˈmeɪdʒər]
n.专业;少校;主修课程;专业课;主修学生
adj.主要的;大的;重要的;严重;大调的
v.主修;专攻

在这里插入图片描述

3、类加载

1、calss cycle
主要分为三部分:loading - linking(Verification Prepareation Resolution) - initializing
在这里插入图片描述

2、双亲委派机制
父加载器不是“父类加载器”,翻译内容不太合适。从子到父检查是否已加载,从父到子进行类的加载。
主要是为了代码安全。(自定义java.lang.string,加入后门则无法保证代码安全)
在这里插入图片描述

4、memory Model (JMM)

4.1 计算机的存储结构

(1) cpu读取数据从上到下依次进行查找。
在这里插入图片描述
各层级读取速度:
在这里插入图片描述
(2)硬件数据一致性
Intel 的芯片底层同步 MESI Cache一致性协议,通过对每个cache line标记不同的状态。
缓存锁比总线锁性能更好,但无法处理不能缓存数据的一致性问题,因此现代cpu一致性=缓存锁+总线锁
(3)缓存行对齐 伪共享问题
缓存读取以cache line为单位,长度为64bytes;(512位)
伪共享问题:通过cache line进行缓存行对齐。

4.2乱序

(1)原因:读指令的同时可以同时执行不影响的其他指令,而写的同时可以进行合并写(WCBuffer合并写)。这样cpu的执行就是乱序的。必须使用Memory Barrier来做好指令排序
volatile的底层就是这么实现的(windows是lock指令)
CPU寄存器执行时间单位1,从内存读取至少100个时间单位,从硬盘读取100万个时间单位级别。cpu为了提高指令执行顺序,会在一条指令执行的过程中,去执行另一条不影响的其他指令。

int a = 1;
int b = a;//这里b依赖a就不会出现乱序问题

(2)合并写技术
(3)保证有序性:
1、件内存屏障(cpu内存屏障,X86):sfence,lfence,mfence。
原子指令,如x86的“lock 。。。”指令是一个full barrier,执行时会锁住内存子系统来确保执行顺序,甚至阔多个CPU。Software Locks 通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。
2、jvm级别如何规范(JSP133)
LoadLodad

4.3 volatile实现细节

(1)编辑器层面 在对应的变量前增加volatile
(2)jvm层面:volatile内存区的读写,都加屏障。
(3)操作系统及硬件层面:

5 对象(后续会补充)

5.1 对象创建

(加载类)
1、class loading
2 class linking (verification, preparation,resolution)
3 class initializing
(创建对象)
4 申请内存空间
5 成员变量赋默认值
6 调用构造方法
1) 成员变量顺序赋初始值
2) 执行构造方法语句 (构造方法首先调用super() 方法)

5.2 对象在内存中的布局

1、对象组成:
1)对象头:markword 8
2) ClassPointer指针
3)实例数据Oops Ordinary Object Pointer/(数组对象的话是:数据长度, 数组数据)
4)对齐,8的倍数

6 JVM Runtime Data Area / JVM Instructions

参考资料(以实际版本为准):
在这里插入图片描述
java语言:
在这里插入图片描述
Run-time data areas

在这里插入图片描述

6.1 JVM Stacks

1、frame - 每一个方法对应一个栈帧
  1) Local Variable Table - 局部变量表
  2) Operand Stack - 操作数栈
  3) Dynamic Linking
  4) return address a()->b() 方法a调用了方法b,方法b的返回值放在什么地方。

6.2 Method Space

    1、Perm Space(<1.8)
字符串常量位于Perm Space
FGC不会清理
大小启动的时候指定,不能改变

    2、Meta Space
字符串常量位于堆
会触发FGC清理
不设定的,最大就是物理内存

6.3 Direct Memory

JVM可以直接访问的内核空间的内存(OS管理的内存)
NIO,提高效率,实现zero copy

6.2常用指令

1、store 存储
2、load 加载指令
3、invoke指令

invoke
英 [ɪnˈvəʊk] 美 [ɪnˈvoʊk]
vt.
援引;援用(法律、规则等作为行动理由);提及(某人、某理论、实例等作为支持);提出(某人的名字,以激发某种感觉或行动)

    1、InvokeStatic 调用静态方法
在这里插入图片描述

    2、InvokeVirtual 自带多态(final修饰的也是这个指令)
    3、InvokeInterface
    4、InvokeSpecial 调用构造方法、私有方法。可以直接定位,不需要多态的方法。
    5、InvokeDynamic JVM最难的指令,lambda表达式或反射或其他动态语言scala kotlin,或者CGLib ,动态产生

7 JVM调优

7.1 Garbage

    没有一个引用指向的一个对象或者一堆对象,都叫garbage

7.2 如何找到Garbage

     Reference Counter 对象被引用几次就有几个的状态值,为0就会被发现。但是无法发现循环引用的一组对象。
     Root Searching跟可达算法,没有根引用的对象

Whice instances are roots?
JVM stacks, native method stack, run-time constant pool, static refences in method area, clazz

7.3 GC Algorithms (常见的垃圾回收算法)

    Mark-Sweep(标记清除)
算法相对简单,存活对象比较多的情况下效率较高。
两边扫描,效率偏低(一次扫描找出存活的,另一边清除G)。
容易产生碎片。
在这里插入图片描述

    Coping(拷贝)
适用于存活对象较少情况,只扫描一次,效率提高。没有碎片
空间浪费,移动复制对象,需要调整对象引用(空间一分为二,将一边的全部移动复制到右边,左边全部清除)
在这里插入图片描述
    Mark-Compact(标记压缩) 没有碎片,效率偏低(需要两次扫描,同时指针需要调整)
在这里插入图片描述

7.4 JVM内存分代模型(用于分代垃圾回收算法)

1、部分垃圾回收器使用的模型

除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代

堆内存逻辑分区(不适用不分代垃圾收集器)
参考链接
在这里插入图片描述

对象的生命周期

2、新生代+老年代+永久代(1.7 Perm Genration)/ 元数据区(1.8)metaspace

a.永久代 元数据 --Class
b. 永久代必须指定大小限制,一旦指定则无法修改(会出现内存溢出问题);元数据可以设置,也可以不设置,无上限(受限于内存空间)
c.字符串常量1.7-永久代 1.8-堆
d.MethodArea逻辑概念 - 永久代、元数据

3、新生代 = Eden + 2个suvivor区

4、老年代
5、GC Tuning(Generation)

a.尽量减少FGC
b.Minor GC = YGC
c.Major GC = FGC

6、对象分配过程

8 常用的垃圾回收器

Garbage Collector与内存大小关系

1.Serial + Serial Old 几十兆 (单线程)
2.Parallel Scavenge + Parallel Old 上百兆 - 几个G (并行回收,垃圾回收器工作时,工作线程停止)
3.PN + CMS 20G(并发模式)
4.G1 上百G
5.ZGC 4T - 16T(13里面支持16个T)

常见垃圾回收器组合

G1比CMS吞吐量少了15%,G1响应时间更快一些

9 调优

9.1 基础概念

1、吞吐量
=用户代码执行时间/(用户代码执行时间+垃圾收集执行时间)
2、响应时间快=用户线程停顿的时间短,STW越短,响应时间越好。
确定调优之前,应该确定到底是哪个优先,是计算型任务还是响应型任务。
科学计算,吞吐量优先的一般:(PS+PO)
响应时间:网站 GUI API (1.8 G1) po调优实战

调优
1、根据需求进行JVM规划和预调优
2、优化运行JVM运行环境(慢、 卡顿)
3、解决JVM运行过程中出现的各种问题

优化环境
1、有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了

为什么?
内存较小,频繁GC,STW时间较长。
为什么更卡顿了
内存越大,GC频率虽然低了,但是FGC时间边长,STW时间过长。
解决措施
将原有的ps+po,更换为pn+cms或者G1

2、系统CPU经常100,如何调优?

CPU100一定有线程在占用系统资源,先确认工作线程占比高,垃圾回收占比高。

  • 找出哪个进程CPU高 (top)
  • 该进程中哪个线程cpu高(top-Hp)
  • 导出该线程的堆栈(jstack)
  • 查找哪个方法(栈帧)消耗时间(jstack)

3、系统内存飙高,如何查找问题?

导出堆内存(jmap)
分析(jhat jvisualvm mat jprofiler…)

4、如何监控JVM

jstat jvisualvm jprofiler arthas top 。。。

调优,从规划开始

  • 调优,从业务场景开始
  • 无监控(压力测试,能看到结果),不调优
  • 步骤
        1、
        2、选择回收器组合
        3、计算内存需求
        4、选定CPU(越高越好)
        5、设定年代大小,升级年龄
        6、设定日志参数

-Xloggc:/opt/xxx/logs/systemname-xxx-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M
-XX:+PrintGCDetails -XX:PrintGCDateStamps -XX:+PrintGCCause
    7、观察日志情况

  • 大流量的处理方法:分而治之
  • 案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G)
1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒)
经验值,
非要计算:一个订单产生需要多少内存?512K * 1000 500M内存
专业一点儿问法:要求响应时间100ms
压测!

  • 案例2:12306遭遇春节大规模抢票应该如何支撑?

12306应该是中国并发量最大的秒杀网站:
号称并发量100W最高
CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器
普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款
12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款
减库存最后还会把压力压到一台服务器
可以做分布式本地库存 + 单独服务器做库存均衡
大流量的处理方法:分而治之

9.2 案例分析

1、入门案例

触发GC代码:

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
 */

public class T15_FullGC_Problem01 {

    private static class CardInfo {
        BigDecimal price = new BigDecimal(0.0);
        String name = "张三";
        int age = 5;
        Date birthdate = new Date();

        public void m() {}
    }

    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
            new ThreadPoolExecutor.DiscardOldestPolicy());

    public static void main(String[] args) throws Exception {
        executor.setMaximumPoolSize(50);

        for (;;){
            modelFit();
            Thread.sleep(100);
        }
    }

    private static void modelFit(){
        List<CardInfo> taskList = getAllCardInfo();
        taskList.forEach(info -> {
            // do something
            executor.scheduleWithFixedDelay(() -> {
                //do sth with info
                info.m();

            }, 2, 3, TimeUnit.SECONDS);
        });
    }

    private static List<CardInfo> getAllCardInfo(){
        List<CardInfo> taskList = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            CardInfo ci = new CardInfo();
            taskList.add(ci);
        }

        return taskList;
    }
}
2、arthas

arthas命令参考文章

10 垃圾回收的算法

10.1 CMS

1、 实现逻辑
1) 初始标记:标记根, GC roots ,此阶段是STW,但是时间很短
2) 并发标记:并发标记阶段,比较耗时间,但是业务程序依然运行;
3) 重新标记:remark阶段,是STW,但是依然很短;
4) 并发清理:多线程并发清理,会产生浮动垃圾(floating Garbage)
在这里插入图片描述

10.2 G1

10.3 三色标记法

11 日志及参数分析

11.1 CMS日志

-XX:+UseConcMarkSweepGC  // 开启cmsGC,默认在young区开启PN

11.2 G1日志分析

-XX:+UseG1GC  // 开启g1

G1不推荐指定young区的大小,通过设置暂停时间,G1会动态调整young区的大小(5%~60%)
G1的调优目标是不要有FGC

11.3 GC常用参数

-Xmn -Xms -Xmx -Xss 年轻代 最小堆,最大堆,栈空间
-XX:+PrintGC -XX:PrintGCDetails
java -XX:+PrintFlagsFinal -version | grep G1 查找G1的相关参数
-Xloggc:opt/log/gc.log 日志打印位置
-XX:MaxTenuringThreshold 升代年龄

11.4 Parallel常用参数

11.5 CMS常用参数

12 纤程(协程)

支持语言:go语言,python。。。
至jdk13都不支持,想用需要引入依赖库

<dependy></dependy>