java jvm
· class cycle(class文件在jvm中的生命周期)
o loading(类加载器)
§ 作用:加载class文件进入内存,在进入时同时创建class对象(一个类可以new 多个对象,但是class对象只有一个),使该对象指向class文件
§ 类加载器的层次
· Bootstrap:加载核心类,C++实现,没有对应的class类,所以没有类加载器对象,默认为null,比如String类的类加载器为null
· Extension:jre/lib/ext/*.jar,比如ZipFileSystem类的加载器
· App:classpath下的指定文件,比如平时自己写的类
· CustomClassLoad:自定义的类加载器
· CustomClassLoad:自定义的类加载器
§ 加载过程
· 自下而上:表示从类加载器层次的自定义加载器到顶级类加载器的过程,首先每个类加载器都是一个launcher类中的内部类(顶级类加载器除外,c++实现),当一个类要被加载进入内存时,首先看有没有自定义的类加载器,每个类加载器都有自己维护的内存,如果内存中没有,就向上查,一直到最顶层
· 自上而下:如果上面所有的类加载器中的内存中都没有,则使用自顶向下的过程,Bootstrap加载器会先在自己的层次范围内查询class,如果有,加载,没有就往下查询,知道最后也没有的话就抛出Class not find 异常
§ 类加载器双亲委派机制实现的原因
· 节省资源
o 先在自底向上的内存中加载,如果有的话可以节省很多时间
· 安全
o 如果自定义了一个核心类,然后打包个客户端,客户端有人使用这个核心类,如果不是这种机制,你可以通过自定义的核心类收集别人的信息(因为是你自己写的),如果是双亲委派机制,一般情况这个核心类其实已经被加载进内存了,所以加载的时候不会加载你自定义的核心类,保证了安全
§ 自定义类加载器
· 说明:类加载的主要过程在Classloader类中,加载类的过程为:findincache()->parent.LoadClass()->findclass(),可以理解为先在缓存中查找,向上委派,再向下委派。当我们要加载一个class文件,如果class不在Java自带的类加载的层次中,通常会爆classnotfind异常,我们可以自定义加载器,执行加载该class,其中修改findclass()方法即可,因为查找之后,发现该类无法加载,最终走到findclass()方法,而我们自己加载class文件,并将其转化为class对象返回即可
· 重要步骤:
o 实现ClassLoader类
o 重写findclass()方法
§ defineClass()方法可以将二进制转换为class对象
· 应用场景:可以将class文件先加密,这样别人就不能加载你的class文件,但是你可以在自定义加载器中加载过程中先将class文件解密,然后实现加载
· 打破双亲委派机制
o 因为双亲委派机制体现在loadclass()方法中,所以重写loadclass方法即可
o 应用:应用程序的热加载过程,每次不需要从cache中去获取,直接从新加载,比如web应用html加载等
§ 程序执行策略:可以用括号中的参数指定
· 混合执行:热点代码编译执行,其余解释执行(-Xmixed)
· 编译执行(-Xint)
· 解释执行(-Xcomp)
o linking
§ verification(检测)
· 主要是检验加载的class是否对虚拟机有害,是否符合规范:
o 1.文件校验
o 2.元数据校验
o 3.字节码校验
o 4.符号引用校验
§ preparation
· 为类变量【静态】分配内存,并赋予初始值(零值)
o 解释:1.不包含final修饰的类变量,因为final修饰的在编译时分配
o 解释2:分配的内存处于方法区,
o 解释3:实例变量会随着对象分配在Java堆中
§ resulation
· 解析的过程就是对类中的接口、类、方法、变量的符号引用进行解析并定位,解析成直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址),并保证这些类被正确的找到。解析的过程可能导致其它的类被加载
o initializing
§ 首先执行静态初始化块static{},初始化静态变量,执行静态方法(如构造方法)
o gc
· 程序执行顺序
o 加载类,并对创建类对象,final static 修饰的变量在编译时就有值了
o 在准备阶段对类变量赋予初始值
o 第一次初始化时执行静态代码块,给类变量赋值
o 构造方法在每次实例化时执行
o 子主题
· 关键字
o volatile
§ 个人理解:最终我们要的结果是指令执行的结果,而指令是在CPU多线程加载副本中的数据生成,这些数据为了保存一致,会频繁的和主内存交互,这样如果刷入主内存的速度足够快,每个副本中的数据是同一个值
§ 特性
· 可见性
o 当前线程副本数据一旦更改,会快速刷入主内存中,所以一定程度上可以提高访问该数据的效率
· 禁止重排序
o 单例双检测时:新建对象的三个步骤后两个顺序禁止替换,所以最终获取到的是初始化之后的对象
· 不保证原子性
o i++问题,当两个线程同时将数据加入副本,其中一个就算是修改了副本快速刷新到了主内存,但是对于另个线程还是继续处理自己的
§ 实现原理
· 写入前后分别加入:StoreStore,StoreLoad屏障
· 读取前后分别加入:LoadLoad,LoadStore屏障
o sychin
· 对象
o new 对象过程
§ 类加载
· ClassLoader
· linking{verification,preparation,resoluation}
· class lintialzing:类变量赋值,执行静态程序
§ 在内存中开辟空间
§ 成员变量赋予默认值
§ 构造方法赋值
· 赋值成员变量
· 执行构造方法
o 对象布局
§ 对象头
· markword,存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳和klass类型指针
§ 实例数据
· 程序代码里面所定义的各种类型的字段内容
§ padding
· 对象实例数据部分没有对齐的话,就需要通过对齐填充来补全
o 对象的访问定位:不同虚拟机不同
§ 句柄
· Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据的具体各自的地址信息
§ 直接指针
· reference存储的是地址,可以直接执行堆内存中的实例数据,堆内存中包含了对象类型地址,指向了方法区中的对象类型(父类,实现的接口等)
o 对象大小
§ 大小获取方式:class 文件load进入内存过程中,可以使用agent代理截取class,获取到instrumentation,通过instrumentation可以获取对象大小{对象大小:对象头,8个字节,成员变量,class_point 指针,padding}
§ Object对象 大小组成:markword 8个字节,klass类型 8个字节(不压缩的话,默认压缩为4),padding ,由于已经是8的倍数,所以为0,总大小为16
§ 如果是数组对象的话需要加上数据长度,比如 new int[],计算方式:8+int 为4+8=20+padding=24
§ 有成员变量需要加上成员变量的大小,如果涉及到String类型,则存储引用类型为point 为4
· jvm常用命令行参数
o 参考文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
o HotSpot参数分类
§ 标准: - 开头,所有的HotSpot都支持非标准:-X 开头,特定版本HotSpot支持特定命令不稳定:-XX 开头,下个版本可能取消
o 内存泄漏和内存溢出
§ 泄漏指的是内存中的某个对象无引用,为垃圾,但是长时间回收不了,溢出时是内存空间不足导致
o 命令:
§ java -XX:+PrintCommandLineFlags HelloGC HelloGC是一个无线训话创建对象并放置数组中的类,运行会冲破内存java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGCPrintGCDetails PrintGCTimeStamps PrintGCCauses GC的详细信息 ,可以用这些命令替换PrintGCjava -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGCjava -XX:+PrintFlagsInitial 默认参数值java -XX:+PrintFlagsFinal 最终参数值java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数java -XX:+PrintFlagsFinal -version |grep GC
· 概念
o jdk:Java开发环境
§ 作用:提供开发环境,让开发人员可以使用其进行对Java语言的编译(jdk中编译器实现),执行
§ 使用方式:配置环境变量,指定classpath路径,使用命令运行
§ 详细概述:jdk(Java Development Kit),java软件开发包,具体包含组件
· jre
· 编译器和调试器
· jvm
· 说明:Java语言编写过程中可能会用到核心库,不然编译会报错,编译命令:Javac,在jdk中有,在jre中没有,事实上,jdk中包含了jre中没有的编译器,调试器和其他工具
o jre:Java运行环境
§ 作用:提供Java运行环境(包含了jvm和核心类库)
§ 使用方式:有了该环境,使用Java命令,可以执行自己写好的class文件
§ 详细概述:主要包含了jvm和核心类库,核心类库也是class文件,事实上在jvm中执行时,也会将核心类库的class文件加载进内存
o jvm:Java虚拟机
§ 作用:跨语言的平台,定义了规范,将class文件中的二进制翻译为可以执行的指令
§ 使用方式:jdk和jre中都有了集成,在使用jdk和jre执行完一个程序时,jvm也用到了
§ 详细概述:是一种规范,规范了class文件二进制应该如何执行,既然是规范,就有很多实现,比如Hotspot,jrockit,j9,当一个Java程序编写完成之后,经过编译就成了class文件,不同的服务器(linux和windows)有不同的jvm实现,但都是可以将class文件变为服务器指令执行,所以在windows上开发的程序,编译为class,在linux上也是可以执行的,并且不同的jvm实现,功能侧重点不同,有些运行速度比较快,有些慢
· 穿插知识:Java属于的语言类型为编译和解释混合
o 编译型语言:比如像c语言等等的这些直接将程序最终形成一个.exe的可执行文件,不可跨平台,原因是像Long等数据类型在不同的服务器长度不同等等
o 解释型语言:将语言边解释边执行,比如Python等等,可以跨平台
o Java执行时需要将class文件解释成为CPU指令,所以是解释型语言,但是Java中有一些常用的包,如果经常使用,会被编译成Java本地指令,这样就不需要再每次进行解释,提高效率,所以又是编译型语言
o 个人理解静态加载类和动态加载类
§ 静态加载:个人理解是在编译时检测该类是否存在,并不像是类加载的那种,加载至内存
§ 动态加载:CLass.forName("XXX"),编译时并不检测,运行到时才检测并加载至内存
· JMM(java memory model)
o 概述:问题产生的原因:当我们的程序运行时,会将主内存中的数据加载至CPU中,CPU进行执行具体的指令执行随着科技的发展,cup速度急速上升,导致CPU和主存执行的速度差异加大,为解决这个问题,在CPU和内存中间加了缓存,缓存空间小,但是访问速度快,目前一般有三级缓存。而一级和二级缓存是和CPU在一起的,三级缓存为共享缓存,CPU访问顺序一般是从小到大,缓存中的数据是以缓存行作为单位的,在早期,多线程访问主存,cpu和内存之间的链接通道(总线)会感知到,当有个数据被某个线程修改时,为防止多线程冲突,会在总线上加锁(总线锁),当一个cpu访问一块数据时,其他CPU不能等待,后期为了增加效率,出现了各种一致性协议(MESI就是其中一种),目前为保证一致性使用的是缓存锁加总线锁(当数据大于64k时),CPU读取缓存中的数据一般也是多条指令一起读,当指令之间没有依赖关系的话,它并不会根据指令顺序去执行,这样就会出现乱序问题。
o 缓存层次
§ 一级缓存,二级缓存,三级缓存(CPU之间共享)
o 排序规范:单个线程下,不会有任何问题,但是多线程下,重排序可能导致问题
§ happen-before原则
· 它告诉我们的开发者,你放心的写并发代码,但是你要遵循我告诉你的原则,你就能避免以上重排序导致的问题。
o catch line 缓存行
§ 缓存中的基本单位,老的CPU为32k
o 伪共享
§ 在CPU的缓存中,一个存储单位为64k的缓存行,相当于8个long类型的数据,比如内存中有8个long类型的数据加入到了两个CPU中,当两个CPU同时更改数据时, 必然有一个的数据会被置为无效数据,这种频繁操作,被称为伪共享
o 总线
§ CPU和内存及其他组件链接的通道
o MESI协议
§ M(modify,修改):数据只在本缓存中,并修改过了,其他缓存需要在本缓存行将数据写会内存,并将该缓存行状态置为E才能操作该缓存的数据
§ E 独享、互斥(Exclusive):该缓存行有效,数据存和内存中一致,一旦发现其他cpu中读取主内存中该数据,将状态改为S
§ S 共享(Shared):数据在其他缓存中也有,且数据相同和主存相比,缓存行必须监听其他缓存是该缓存行无效或者独享该缓存行的请求,并将该缓存行置为I状态
§ I 无效(Invalid):该缓存行数据无效
o Java内存屏障
§ 硬件层级
· sfence:写屏障
· lfence:读屏障
· mfence:读写屏障
· Lock前缀
§ Java内存屏障
· LoadLoad 屏障:序列:Load1,Loadload,Load2,load1加载至load2之前
· StoreStore 屏障:序列:Store1,StoreStore,Store2,确保store1指令执行结果可见,在store2指令写入之前
· LoadStore 屏障:序列: Load1; LoadStore; Store2,确保Store1指令在Store2指令刷新之前读取
· StoreLoad 屏障:序列: Store1; StoreLoad; Load2,确保store1指令可见,在store2指令读取之前
· 垃圾处理
o 垃圾回收算法:
§ 标记清除:第一步找到所有垃圾并标记,第二步,清除所有标记的垃圾,位置不连续,容易产生碎片,效率低
§ 拷贝算法:将内存分为两块,其中一块使用,一旦内存用完,将所有存活的copy至另一块区域,清除使用过的这一块区域,没有碎片,但是浪费空间
§ 标记压缩:先标记,然后清除,最后将所有存货的对象放置在一起(压缩),指针需要调整
o 垃圾:没有引用指向的对象就是垃圾
o 垃圾定位算法
§ 根可达算法:从根节点开始查找,和根节点没有关联的对象,经过多次标记之后,可看成是垃圾
§ 引用计数算法:该对象没有和其他对象形成关联关系,即没有进行标记的(该算法有循环引用的缺点)
o 垃圾并发标记算法
§ 三色标记算法
· 白色:未被标记
· 灰色:对象标记,但其引用未被标记
· 黑色:已经标记完成
§ 三色标记算法的缺陷:当灰色A中有个引用c指向B,在标记线程从标记队列中拉取出来A标记时,任务线程改动了c=C,则导致B漏标
· cms解决方案
o 写屏障 + 增量更新
§ 【当有新引用插入进来时,记录下新的引用对象】关注引用的增加,将黑色标记为灰色,下次扫描从新扫描属性
· G1中解决方案
o Snapshot At The Beginning,SATB,关注引用的减少,将引用加至集合,下次扫描集合就可以拿到
o jvm内存分代模型
§ 介绍:逻辑分代模型是指在逻辑上进行分代,可以理解为分区,大概分为新生代和老年代
§ 老年代:垃圾回收多次都没有回收掉的老对象放置区
§ 新生代:刚new的对象放置的区域
· eden 占比为8
· surever(幸存者s1):占比为1
· surever(s2):占比为1
§ 分代空间参数设置:-Xms x为非标参数,m为memory,s为最小值,综合表示堆的最小heap值,一般启动时会按照最小值启动 -Xmx 第二个表示max,最大值,综合表示为heap的最大值 -Xmn n表示new ,即新生代的值 注意:-Xms一般设置和-Xmx一样大,因为当这个值不断变化时,会在变化之前触发GC,效率比较低,尤其是一台服务器上只有一个应用时,设置合适值即可,只有多个进程时才有可能触发
§ 分代模型值对象的消亡:首先进入Eden区,然后是s1,s2,s1和s2会循环部分时间,当对象年龄到某一个值后,会进入到old区。这个值可以通过参数配置:-XX:MaxTenuringThreshold:s1和s2之间的关系:看链接
§ s1和s2之间的关系对象的分配:
· 栈:小对象分配到栈,比如方法中使用,其他地方不再引用,线程私有小对象,无逃逸(没有在外面引用)
· 堆:
o 本地分配:在堆上分配时先在线程本地分配Thread Local Allocation Buffer TLAB,线程防止线程抢占内存空间,给线程分配自己的线程本地
o 参数:-XX:-DoEscapeAnalysis 去掉逃逸分析 -XX:-EliminateAllocations 去掉标量替换 -XX:-UseTLAB 去掉TLAB
o 堆分配:优先线程本地,然后堆
§ 垃圾回收器
· Serial
o 介绍:串行,单线程回收,当它回收时,会在安全点(safe point)触发SWT(stop the world),所有工作线程停止,回收之后再进行,效率低
o 分代:serial为新生代,serial old为收集老年代
o 算法:标记清除算法
o 介绍:并行,会在安全点(safe point)触发SWT(stop the world),所有工作线程停止,回收之后再进行
· Parallel Scavenge
o 分代: Parallel Old为老年代
o 算法:标记清除算法(Mark-Sweep)
· ParNew
o 介绍:Parallel Scavenge上进行了改进,除算法以外,其他基本一致
o 算法:Copying算法
· CMS
o 四个步骤
§ CMS initial mark 初始标记阶段,只标记root节点的
§ CMS remark 重新标记阶段,这也是一个STW,在并发标记阶段产生的新垃圾进行标记
§ CMS concurrent mark 并发标记阶段,大多时间,客户端有可能感到停顿
§ CMS concurrent sweep 并发清理阶段,这个阶段没有swt,如果有新的垃圾则在下一个cms清除
o 触发条件:老年代分配不下了会触发CMS,其初始标记是单线程,重新标记是多线程。
o 缺点:如果内存很大,一旦老年代产生了很多内存碎片的时候,从年轻代进入到老年代的对象就找不到空间了-PromotionFailed,这时,CMS请出了Serial Old这个上古时代的回收器使用单线程进行标记压缩,那效率就可想而知了
o 参数:-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默认为0 指经过多少次FGC才进行压缩 -XX:CMSInitiatingOccupancyFraction 92% 可以降低该值,让老年代有足够的空间
· G1
o 优点:G1在GC过程中会进行整理内存,不会产生很多内存碎片 G1的STW更可控,可以指定可期望的GC停顿时间
o -XX:+UseG1GC 是必须的,它用来告诉 jvm 开启 G1 垃圾收集器
o -XX:MaxGCPauseMillis 用来设置最大停顿时间,由于 G1 拥有垃圾回收时间的预测机制,因此他可以保证在你设置的最大停顿时间内完成垃圾回收,这可以说是 G1 垃圾回收器最令人惊喜的特性了
o G1的内存空间逻辑上分为4块,eden,surever,old,Humongous , G1 中新增了 Humongous 存储区域,这部分空间用来存储超过 Region 大小一半的超大对象
o 对象分配策略
§ TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
· 防止线程抢夺空间
§ 在 Eden 区进行分配
· 当TLAB中分配不下时,在eden的region中分配
§ 在 Humongous 区进行分配
· 当对象大小超过region的一半时
o G1 的优势和不足
§ 优势在于将堆内存空间离散化,从而提升对象分配和回收的效率,减少内存碎片的产生,G1 垃圾回收器还拥有停顿预测模型,从而能够尽量满足用户所设定的 GC 预期停顿时间,从而让整个回收过程更加可控
§ G1 的停顿预测功能并不能将回收过程精准的保证在所设定的时间范围内
o 参数:
§ -XX:G1HeapRegionSize=n 设置Region大小,并非最终值,因为最终只能取 2 的幂大小
§ -XX:MaxGCPauseMillis 设置G1收集过程目标时间,默认值200ms,不是硬性条件
§ -XX:G1NewSizePercent 新生代最小值,默认值5%
§ -XX:G1MaxNewSizePercent 新生代最大值,默认值60%
§ -XX:ParallelGCThreads STW期间,并行GC线程数
§ -XX:ConcGCThreads=n 并发标记阶段,并行执行的线程数
§ -XX:InitiatingHeapOccupancyPercent 设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous
o RSet和 Card Table
§ RSet
· 用来记录外部指向本Region的所有引用,该集合用来记录并跟踪其它Region指向该Region中对象的引用
§ Card Table
· Card: JVM将内存划分成了固定大小的Card。这里可以类比物理内存上page的概念。一个 Card Table 将一个 Region 划分为 128 到 512 字节之间为单位的若干个 Card,通过标记 Card 是否为脏及是否被引用,卡表记录未0的年老代区域没有任何对象指向新生代,只有卡表位为1的区域才有对象包含新生代引用,因此在新生代GC时,只需要扫描卡表位为1所在的年老代空间
§ Cset
· 表格记录了哪些对象需要回收
o G1的fullGC:jdk10之前为串行的,所以尽量不要出现FullGC
§ 解决
· 加内存
· 提高硬件CPU性能
· 调节参数:Mixed GC,默认为45%,相当于CMS,提前gc
· ZGC
o 介绍:ZGC是一种并发的、不分代的、基于Region且支持NUMA的压缩收集器。因为只会在枚举根节点的阶段STW, 因此停顿时间不会随着堆大小或存活对象的多少而增加
o 目标:垃圾回收停顿时间不超过10ms 无论是相对小的堆(几百MB)还是大堆(TB级)都能应对自如 与G1相比,吞吐量下降不超过15% 方便日后在此基础上实现新的gc特性、利用colored pointers和读屏障进一步优化收集器
· 垃圾回收器组合参数设定
o -XX:+UseSerialGC = Serial New (DefNew) + Serial Old :小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
o -XX:+UseParNewGC = ParNew + SerialOld :这个组合已经很少用(在某些版本中已经废弃)https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
o -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old :()中的有些版本需要,有些不需要
o -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
o -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
o XX:+UseG1GC = G1
o Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC :java -XX:+PrintCommandLineFlags -version 或者通过GC的日志来分辨
o -XX:InitialHeapSize=534795072 -XX:MaxHeapSize=8556721152 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
o java version "1.8.0_152"
o Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
o Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)
o GC触发
§ yang
· 多线程并行执行
· eden内存不足
§ full gc
· old区域不足
· System.gc()
· jvm内存逻辑划分
o 栈
§ 栈帧
· 局部变量表:存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类原始数据类型、对象引用(reference),以及returnAddress类型,JVM使用局部变量表来完成方法调用时的参数传递,访问索引为0的Slot一定存储的是与被调用实例方法相对应的对象引用
· 操作数栈:后进先出(Last-In-First-Out)的操作数栈,操作数栈就是JVM执行引擎的一个工作区,当一个方法被调用的时候,一个新的栈帧也会随之被创建出来,但这个时候栈帧中的操作数栈却是空的,只有方法在执行的过程中,才会有各种各样的字节码指令往操作数栈中执行入栈和出栈操作
· 动态链接:每一个栈帧内部除了包含局部变量表和操作数栈之外,还包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking),在一个字节码文件中,描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用(Symbolic Reference)来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
· 方法返回值,无论当前方法的调用结果是正常还是异常,都需要在执行完成之后返回到之前被调用的位置上,那么这个时候当前栈帧就承担着恢复调用者状态的责任
o 堆
§ 老年代
§ 新生代
· eden
· surever(1和2)
o 方法区
§ 1.8之前称为:永久区
§ 1.8之后称为metaspace
§ 两版本的区别:永久区需要在程序启动时指定大小,metaspace的大小和实际物理内存相关,字符串常亮之前在永久区,1.8之后再堆中
· jvm调优
o 概念
§ 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
· 科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)
§ 响应时间:STW越短,响应时间越好
· 网站 GUI API (1.8 G1)
o 所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量
§ 根据需求进行JVM规划和预调优优化运行JVM运行环境(慢,卡顿)解决JVM运行过程中出现的各种问题(OOM)
o 步骤:调优,从业务场景开始,没有业务场景的调优都是耍流氓无监控(压力测试,能看到结果),不调优
§ 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]选择回收器组合计算内存需求(经验值 1.5G 16G)选定CPU(越高越好)设定年代大小、升级年龄设定日志参数-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause或者每天产生一个日志文件观察日志情况
o 案例
§ 案例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) 同时异步进行 ->等付款减库存最后还会把压力压到一台服务器可以做分布式本地库存 + 单独服务器做库存均衡大流量的处理方法:分而治之
§ 案例3
· 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G
· 的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G
· 的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了
o 为什么原网站慢?很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢为什么会更卡顿?内存越大,FGC时间越长咋办?PS -> PN + CMS 或者 G1或者nginx方式,将新的分为几台,代理请求
§ 案例4
· 系统CPU经常100%,如何调优?(面试高频)
· CPU100%那么一定有线程在占用系统资源
o 找出哪个进程cpu高(top)
o 该进程中的哪个线程cpu高(top -Hp)
o 导出该线程的堆栈 (jstack)
o 查找哪个方法(栈帧)消耗时间 (jstack)
o 工作线程占比高 | 垃圾回收线程占比高
§ 面试
· 系统内存飙高,如何查找问题?
o 导出堆内存 (jmap)
o 分析 (jhat jvisualvm mat jprofiler ... )
· 如何监控JVM
o jstat jvisualvm jprofiler arthas top...
o 具体操作:
§ java -Xmn1M -Xms4M -Xmx6M -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC -jar jvm-1.0-SNAPSHOT.jar 年轻代内存 垃圾回收器为ParNew + CMS + Serial Old 输出GC信息 java jar 不断产生对象
§ 步骤:
· 一般是运维团队首先受到报警信息(CPU Memory)
· 定位问题
o 方式
§ top命令观察到问题:内存不断增长 CPU占用率居高不下
§ top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
§ jps定位具体java进程jstack 定位线程状况,重点关注:WAITING BLOCKEDeg.waiting on <0x0000000088ca3310> (a java.lang.Object)一定要找到是哪个线程持有这把锁怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE,所以阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称(自定义ThreadFactory)
§ jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)jstat -gc 4655 500 : 每个500个毫秒打印GC的情况
§ 面试:如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误)1:已经上线的系统不用图形界面用什么?(cmdline arthas)2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)
§ jmap - histo 4655 | head -20,查找有多少对象产生
§ jmap -dump:format=b,file=xxx pid :
· 线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
· 1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
· 2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
· 3:在线定位(一般小点儿公司用不到)
· 使用MAT / jhat /jvisualvm 进行dump文件分析https://www.cnblogs.com/baihuitestsoftware/articles/6406271.htmljhat -J-mx512M xxx.dump 分析这个份dump的数据 jvisualvm分析直接导入dump下来的文件即可http://192.168.17.11:7000 7000为端口,可以通过访问端口访问信息拉到最后:找到对应链接可以使用OQL查找特定问题对象
· jconsole远程连接
o java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
· jvisualvm
o https://www.cnblogs.com/liugh/p/7620336.html
· arthas在线排查工具
o 在线排查的原因:生产环境一般不会让停止服务器,也不允许dump,所以在线使用命令排查比较使用频繁,还有就是arthas可以更改线上已经加载到内存的class
o 命令:
§ jvm观察jvm信息
§ thread定位线程问题
§ dashboard 观察系统情况
§ heapdump + jhat分析jad反编译动态代理生成类的问题定位第三方的类(观察代码)版本问题(确定自己最新提交的版本是不是被使用)redefine 热替换目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性m() -> mm()
§ sc - search classwatch - watch method没有包含的功能:jmap
o 命令总结:
§ GC常用参数
· -Xmn -Xms -Xmx -Xss年轻代 最小堆 最大堆 栈空间-XX:+UseTLAB使用TLAB(Thread Local Allocation Buffer ),默认打开,减少线程占用空间-XX:+PrintTLAB打印TLAB的使用情况-XX:TLABSize设置TLAB大小-XX:+DisableExplictGCSystem.gc()不管用 ,FGC-XX:+PrintGC-XX:+PrintGCDetails-XX:+PrintHeapAtGC-XX:+PrintGCTimeStamps-XX:+PrintGCApplicationConcurrentTime (低)打印应用程序时间-XX:+PrintGCApplicationStoppedTime (低)打印暂停时长-XX:+PrintReferenceGC (重要性低)记录回收了多少种不同引用类型的引用-verbose:class类加载详细过程-XX:+PrintVMOptions-XX:+PrintFlagsFinal 设置了某个jvm值之后获取的最终值 -XX:+PrintFlagsInitial jvm的初始参数值 必须会用-Xloggc:opt/log/gc.log-XX:MaxTenuringThreshold升代年龄,最大值15,每经过一次gc,年龄增加一次, 对象在Eden出生如果经第一次Minor GC后仍然存活, 且能被Survivor from容纳的话, 将被移动到Survivor空间中, 并将年龄设为1. 以后对象在Survivor区中每熬过一次Minor GC年龄就+1(发生gc时,from区的对象会根据年龄决定去to区还是old区,eden全部copy到to区,清空eden和from,替换from和to,也就是说这时的to就是from,同一时间,只有一个surever使用)锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ...这些不建议设置
§ Parallel常用参数
· -XX:SurvivorRatio 设置为8,表示eden和sruecer的比例为8:1:1,设置为n,Eden大小即是幸存区from的N倍-XX:PreTenureSizeThreshold 意思是超过这个值的时候,对象直接在old区分配内存大对象到底多大-XX:MaxTenuringThreshold 当达到这个年龄,from区的对象会迁移至old区域-XX:+ParallelGCThreads并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同-XX:+UseAdaptiveSizePolicy自动选择各区大小比例
§ CMS常用参数
· -XX:+UseConcMarkSweepGC-XX:ParallelCMSThreadsCMS线程数量-XX:CMSInitiatingOccupancyFraction使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)-XX:+UseCMSCompactAtFullCollection在FGC时进行压缩,减少碎片-XX:CMSFullGCsBeforeCompaction多少次FGC之后进行压缩-XX:+CMSClassUnloadingEnabled CMS收集器默认不会对永久代进行垃圾回收。如果希望对永久代进行垃圾回收,可用设置标志-XX:+CMSClassUnloadingEnabled-XX:CMSInitiatingPermOccupancyFraction 70是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC);达到什么比例时进行Perm回收GCTimeRatio设置GC时间占用程序运行时间的百分比-XX:MaxGCPauseMillis停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
§ G1常用参数
· -XX:+UseG1GC-XX:MaxGCPauseMillis建议值,G1会尝试调整Young区的块数来达到这个值-XX:GCPauseIntervalMillisGC的间隔时间-XX:+G1HeapRegionSize分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长ZGC做了改进(动态区块大小)G1NewSizePercent新生代最小比例,默认为5%G1MaxNewSizePercent新生代最大比例,默认为60%GCTimeRatioGC时间建议比例,G1会根据这个值调整堆空间ConcGCThreads线程数量InitiatingHeapOccupancyPercent启动G1的堆空间占用比例
o gc日志
PS日志格式
日志前半部分total = eden + 1个survivor ,日志后半部分eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址

G1垃圾回收器日志

Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC :java -XX:+PrintCommandLineFlags -version 或者通过GC的日志来分辨


浙公网安备 33010602011771号