JVM

.class文件什么情况下会被加载?

JVM启动进程后,会寻找程序入口,包含Main方法的类,Main方法执行过程里用到哪些类就加载哪些类

正常的war程序如何加载的? Tomcat本身是一个java程序,Web程序只是他下面的子程序,目前不知道他的加载机制,Tomcat搞一个Main方法作为程序入口也不是不可以

.class文件加载的过程

加载主要分成3个部分

  • 加载

    将静态文件数据加载到方法区,同时在堆内存生成一个代表对应类的Class对象

  • 链接

    • 验证

      确保Class文件内容符合java规范

    • 准备

      为类变量分配内存、并为其设置初始值 public static int x = 123 在准备阶段过后,x的        值是0而非123。真正的赋值操作是在这个类初始化时进行

    • 解析

      将运行时常量池中的符号引用替换为直接引用,类编译时期是不知道他所依赖的类的具体内存位置的,所以用符号来代表引用关系,直接引用就是真实地址,"书里对这快的描述是JVM没有限制解析阶段具体的触发时间,只是规定了在某些操作符号引用的字节码指令之前,先对他们所使用的符号进行解析 "

  • 初始化

    1)父类——静态变量

    2)父类——静态代码块

    3)子类——静态变量

    4)子类——静态代码块

    5)父类——非静态变量

    6)父类——非静态代码块

    7)父类——构造器

    8)子类——非静态变量

    9)子类——非静态代码块

    10)子类——构造器

  • 卸载

    class文件被加载到元空间,当满足以下3个条件时表示他可被GC

    类加载器对象已经被回收

    Class对象不存在引用关系

    没有该Class对应的实例对象

类的加载是基于双亲委派模型,双亲委派模型的出发点/好处

启动类——加载器

扩展类——加载器

应用程序类——加载器

自定义——加载器

首先要说明,同一个class文件被不同类加载器加载,这两个类是不相同的

双亲模型保证了JDK基础类只能被特定类加载器加载且只能加载一次,这样就不会出现用户在ClassPath下写一个Object类被加载,导致应用出现多个Object类致使程序混乱

既然启动类加载器是加载lib包下的类,写一个类扔里面会不会被加载?

常见的类加载器?

为什么说Tomcat破坏了双亲委派模型
首先说双亲委派模型要求类的加载要先交由父加载器去做
容器有几个特点
要加载相同jar包的不同版本,默认加载机制,仅根据类名判断是否加载成功,那是做不到这点
JSP要动态生效,默认加载机制,JSP改了内容但类名没变不会再次加载
Tomcat自定义了加载器,针对这种隔离情况做了特殊处理,即自己加载不向父加载器抛

内存区域划分
  • 程序计数器 【线程私有】

    用于记录每个线程执行到了哪行代码

  • 虚拟机栈 【线程私有】

    生命周期与线程相同,方法被执行时会同时创建一个栈帧,用于存储

    局部变量表(指向堆中的对象)

    操作数栈

    动态连接(方法调方法)

    方法出口,即返回地址及返回方法调用的位置

  • 本地方法栈

  • 方法区

    1.8后方法区从堆中移除,放到了叫元空间MetaSpace,元空间在本地内存,不再受堆空间大小限制,存储类元信息

    验证

    循环cglib动态生成类

    1.7 java.lang.OutOfMemoryError : MetaSpace space

    1.8 java.lang.OutOfMemoryError : PermGen space

  • 对象、字符串常量池、静态变量

    验证

    字符串常量池

    ​ 循环拼接字符串

    静态变量

    ​ 定义大的静态数组 private static Long array_1… = new Long(9999999);

    最终都是堆溢出 java.lang.OutOfMemoryError : Java heap space

    新生代

    ​ 分为一个Eden和两个Survivor(From、To),流转过程:

    ​ 创建对象分配内存时,发现Eden区快要满了,触发Minor GC,Eden区和From中存活的对象放入To,

    ​ 清空Eden、From,同时将From、To对调

    老年代

    ​ 进入老年代的情况

    ​ 1、对象年龄达到15后

    ​ 2、GC过后Survivor区不够存,存活对象直接全放到老年代

    ​ 3、大对象直接进入老年代,默认3M,新对象大于他不管Eden是否够用,直接进入老年代

    ​ 4、动态判断,From区某个年龄的对象大于空间的50%,则大于等于该年龄的对象进入老年代

    ​ 实际上计算规则和描述不太一样,是年龄1+年龄2+年龄n总和超过50%,移动年龄大于n的对象

Minor GC

复制算法

触发时机

执行过程

  • 首先将老年代剩余空间与新生代对象总量比较,如果大于则正常执行Minor GC,如果小于
  • 判断是否开启空间担保选项,如果未开启,则将本次Minor GC升级为Full GC,如果开启
  • 判断老年代剩余空间是否大于平均晋升对象占空间大小,如果小于,则将本次Minor GC升级为Full GC,如果大于
  • 尝试进行Minor GC(本来就是Minor GC,检查完真正开始执行,这才是广义上的Minor GC过程)
    • Survivor区够存,那么进入Survivor即可
    • Survivor区不够,但是老年代剩余空间够存,那就直接进入老年代,进入老年代还有几个情况,对象年龄达到15、超过设定值的大对象(默认3M)、同一个年龄的对象空间大于From区50%
    • Survivor区不够,并且老年代剩余空间也不够,本次Minor GC升级为Full GC
Full GC

Full GC可以认为是老年代GC,"老年代GC前会先进行一次新生代GC"是可配的,老年代很多对象都会引用到新生代的对象,先进行一次Minor GC可以提高老年代GC的速度

标记-清理算法

触发时机

  • Minor GC之前,检查老年代剩余空间小于新生代对象总量,并且不满足Minor GC条件(1.未开启空间担保 2.开启空间担保但老年代剩余空间小于平均晋升空间),将触发Full GC
  • Minor GC之后,老年代剩余空间小于晋升的对象空间,将触发Full GC
  • 方法区(MetaSpace)空间不足
  • 显式System.gc
  • 如果使用CMS回收器,并且老年代内存占用达到了一定比例

执行过程

Full GC只是一个概念,表示对整个堆和方法区进行回收,就是让各个区分别使用不同的垃圾收集器进行GC

Full GC等同于Old GC这种说法,本质上是上面的条件实际上触发的是Full GC,进而引发新生代、老年代、分发区的GC

垃圾回收器

CMS

标记-清理(即支持清理又支持整理,并且默认是整理)

有两个参数控制整理行为

1、GC过后把对象整理到一起避免碎片,默认是开启的

2、还有个参数表示经过多少轮GC后开始一次碎片整理,默认0表示每次都整理)

在CMS遇到空间不足时,可以使用串行收集器作为后备

触发条件

  • 本质就是Full GC条件
  • 老年代内存占用达到一定比例,默认92%,自动触发
  • 元空间容量不足

处理过程

  • 初始标记 【STW】

    单线程执行,标记直接能被GC Root和新生代引用到的对象

  • 并发标记

    并发执行,由初始标记的对象开始,标记所有能被引用的对象

  • 并发预清理

    并发执行,这个过程可能产生新的对象或垃圾,在这里提前对其中一部分情况的对象做标记,这里和重新标记做的都是同一个事,这里是为了减小重新标记的压力,此阶段标记从新生代晋升的对象新分配到老年代的对象以及在并发阶段被修改了的对象

  • 重新标记 【STW】

    并发执行,再次对引用关系进行确认

  • 并发清理

G1

复制算法

对整个堆进行管理,即用一种回收器处理新生、老年代的回收

适合作为大内存的回收器,因为内存越大回收时间越长,G1可以控制回收时间

将内存分割成大小相同的区域

延续了新生代、老年代的概念,即某些块属于新生代某些块属于老年代

回收时间是可控的,利用**“回收价值”**做到这点,他知道每个块有多少垃圾回收要多长时间,在有限时间内尽可能多回收垃圾

触发条件

  • 新生代块达到60%开始新生代垃圾回收
  • 老年代块达到45%开始混合GC
ParNew

复制算法

多线程处理,线程数默认为CPU核心数

某个服务的 CPU使用率很高,甚至达到90%,如何排查?

两种原因

  1. 正常耗资源业务处理,如规则引擎
  2. 频繁Full GC导致CPU占用过高

定位过程

  1. 使用**【TOP】**命令观察CPU和内存使用情况

    先观察一段时间,因业务里确实有耗资源操作

    发现持续飙高,怀疑是GC

  2. 使用**【jstat -gc PID 1000 5】**命令查看JVM使用情况(每隔1000毫秒统计一次,执行5次)

    显示内容包括新生代(Eden、Survivor)、老年代、方法区内存占用情况,YGC、FGC次数和总耗时

    可以通过这些信息统计出

    一、各个区的对象增长速度,即每次统计的值相减

    二、Young GC触发频率

    三、每次Young GC有多少对象进入老年代

    发现Full GC次数比较多,所以要定位是什么对象占用空间比较多

    这里只是估算,可用JVM运维监控工具OpenFalcon进行监控,监控JVM变化过程,预警(短时间超过3次GC就报警)并通知

  3. 使用**【jmap -histo PID】**命令,查看对象分布,这个命令会按照对象占用空间从大到小进行排列输出

    (这里只是个大概信息,要详细的就用【jmap -dump】取出堆快照信息)

Full GC频率过高?

两种原因

  1. JVM参数设置不合理,导致对象频繁进入老年代

    这个和系统业务类型、处理能力有关,举个例子,4核8G,参数默认,JVM内存默认1/4物理内存分得2G,新生代/老年代默认1:2,新生代分700M,Eden分560M,Survivor分70M

  2. 程序问题,存在大量回收不掉的对象

JVM参数优化?

参数优化最核心就是:通过调整各个空间比例,使对象尽量在新生代分配和回收,减少Full GC次数

三个影响因素

  • 资源情况,即部署多少机器,机器什么配置,资源充足机器管够不需要什么优化
  • 业务类型,SF这种属于单量不大,但都是大对象,单个报文能达到2M,某些业务能达到10M,这些都要转成对象再做处理,其他业务如电商,单量大但流程相对简单,对象较小
  • 业务总量,量小谈不上优化

这三点直接影响JVM内存占用情况的估算,不过这种估算不太靠谱,占用情况是受全部业务影响,很难准确判断,可以一边压测一边用监控工具观察

-Xmx 堆最大值,默认为物理内存的1/4

-Xms 堆初始值,默认为物理内存的1/64,一般这俩设置成一样

-Xmn 新生代大小,剩下的就是老年代 新:老默认1:2

-XX:MetaspaceSize 元空间触发GC阈值,默认20.8M 其对应的最大值默认是没有限制

-Xss 每个线程的栈大小,默认1M

4核8G,JVM分得4G左右内存

-Xmx 堆最大值, 3072M 设为3G,因大对象较多

-Xmn 新生代大小,2048M 设为2G,因大对象较多

-XX:MetaspaceSize 元空间触发GC阈值,256M 设为265M,因模版引擎使用到字节码操作,动态生成类

没有直接参与,但说一下思路

首先,说一下大的背景

资源情况,4核心8G,4个机器是一组

业务类型,大报文,处理时间长

业务总量,每天6万单,集中在早上9、10点

什么是最优不好说,拿一个默认的JVM,没做过调整的JVM

4核8G

堆默认为1/4物理内存,即2G

新生代老年代默认1:2,即新生代700M Eden分560M,Survivor分70M + 70M

老年代1.4G

单量为每天6万,集中在早上9、10点两个小时(这个时间段基本只有下单操作)

每小时3万,每分钟500,每秒8,4个机器,每个机器每秒分2个单

每单报文1M左右,加上各种校验、信息补全、系统交互等等,算翻5倍,每单要占用5M,每秒2单,即10M/S,每个任务要执行10s左右

  • 看多长时间会Young GC(Eden区多长时间会满)

    700M / 10M/s = 1分钟左右就满了

  • 看Young GC后多少对象进到老年代

    每秒2单,每单执行耗时10秒,所以当Eden满的时候,还有20单没执行完,当GC过后这20单不会被回收,即有200M左右不会被回收

    Survivor为70M,不够存,所以GC后的对象会直接进到老年代,即每分钟有200M进入老年代

    当然这个还有细节,每次Minor GC要判断老年代剩余空间是否大于新生代,并且还要根据空间担保策略判断

  • 看多长时间就会触发Full GC(老年代多长时间满)

    1.4G / 200M/分 7分钟左右就满了,相当于每7分钟就会触发一次Full GC

怎么优化?

这个例子能看出几个问题

  1. JVM资源分配不足,一般不用默认1/4,4核8G可以给到4G的内存
  2. 新生代和老年代比例不合理,导致Survivor区一直存不下Minor GC下来的晋升对象使其直接进入老年代
  3. 因为业务类型特殊,全是1M左右大对象,所以可能直接绕过Survivor区,直接进入老年代

解决办法

  1. 多给JVM分配一些内存,调整到4G
  2. 调整新生代老年代的比例,使得Survivor区资源够用,Eden对象可以晋升
  3. 适当调整大对象限制,默认3M,因为业务上是大对象,可能超过3M导致直接进入老年代

最终目的是为了让对象都在新生代里分配和回收,减少Full GC的频率

运维监控工具 OpenFalcon

JVM运维工具,监控JVM变化过程,预警(短时间超过3次GC就报警)并通知

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值