一文了解jvm

在这里插入图片描述

1.模块

在这里插入图片描述
左侧为线程共享模块,右侧是线程私有模块(随线程安生和消亡,不考虑内存回收)。
方法区/永久代
用于存储已经被虚拟机加载的类信息,常量(“zdy”,"123"等),静态变量(static变量)等数据,可用以下参数调整:
jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8以后大小就只受本机总内存的限制

几乎所有对象都分配在这里,也是垃圾回收发生的主要区域,可用以下参数调整:
-Xms:堆的最小值;
-Xmx:堆的最大值;
-Xmn:新生代的大小;
-XX:NewSize;新生代最小值;
-XX:MaxNewSize:新生代最大值;
程序计数器
当前线程执行的字节码的行号指示器,占用空间小,也无法干涉。
虚拟机栈
每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,方法出口等信息,然后放入栈。方法的执行和结束就对应着栈帧的入栈和出栈操作。
本地方法栈
本地方法栈保存的是native方法的信息
在这里插入图片描述

2.垃圾存活标准

1)引用计数法
在对象头维护着一个 counter 计数器,对象被引用一次则计数器 +1;若引用失效则计数器 -1。当计数器为 0 时,就认为该对象无效了
2)可达性分析法
从GC Roots 为起点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
GC Roots 是指:
Java 虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈中引用的对象
方法区中常量引用的对象
方法区中类静态属性引用的对象
3)java中的引用

  • 强引用
    什么时候都不会被回收
  • 软引用
    只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OOM 之前,清理软引用指向的对象。
  • 弱引用
    当 JVM 进行垃圾回收时,无论内存是否充足,都会回收只被弱引用关联的对象。
  • 虚引用
    为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

3.垃圾回收算法

1)标记-清除(Mark-Sweep)算法
①标记阶段:首先通过根节点,标记所有从根节点开始的可达对象。未被标记的对象就是未被引用的垃圾对象
②清除阶段:清除所有未被标记的对象。
缺点:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存不得不提前触发另一次垃圾收集。
2)复制(Copying)算法
①将原有的内存空间分为两块,每次只使用一块,
②在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,然后清除正在使用的内存块中的所有对象。
③交换两个内存的角色,完成垃圾回收。
这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点也是明显的,可用内存缩小到了原先的一半。
3)标记-整理(Mark-Compact)算法
①标记阶段:先通过根节点,标记所有从根节点开始的可达对象,未被标记的为垃圾对象
②整理阶段:将所有的存活对象压缩到内存的一段,之后清理边界外所有的空间
适合用于存活对象较多的场合,如老年代。
内存利用率: 标记整理 > 标记清除 > 复制
4)分代收集算法
根据对象存活周期的不同将内存划分为几块。
一般是将Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法
①Java堆是JVM管理的最大一块内存空间,主要存放对象实例。

  • 堆被分为两块区域:新生代 young、 老年代old
  • 堆大小=新生代+老年代 (新生代占堆空间的1/3、老年代占堆空间2/3)
    在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用“复制算法”,只需要付出少量存活对象的复制成本就可以完成收集。
    在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
    新生代又被分为了eden、from survivor、to survivor(8:1:1)
    在这里插入图片描述
    新生代这样划分是为了更好的管理堆内存中的对象,方便GC算法—复制算法来进行垃圾回收。 JVM每次只会使用eden和其中一块survivor来为对象服务,所以无论什么时候,都会有一块survivor空间,因此新生代实际可用空间只有90%。
    如果只有一个svrvivor,一遍新生代 gc 过后,活着的对象全部进入老年代,即便它在接下来的几次 gc 过程中极有可能被回收掉。这样老年代很快被填满, Full GC 的频率大大增加。
    在新生代经过15次gc,会转移到老年代去。-XX:MAX TenuringThreshold默认15
    创建一个新对象的分配流程
    在这里插入图片描述
    classloader设计的目的:
    保证唯一性+保证安全(保证了底层的类一定是预先加载的,保护jdk)
    调优经验:
    高吞吐和低延迟一直都是JVM调优的最终目标,一般计算任务和组件服务会偏向高吞吐,而web展示则偏向低延迟才会带来更好的用户体验。
    调优之前可以先用-XX:+PrintFlagsFinal来查看虚拟机是否默认开启某参数,不同版本的JDK可能虚拟机默认开启的参数也略有不同。
java -server -XX:+PrintCommandLineFlags |grep XXXXXXX

4.jvm调优经验

1)原因分析
Javaheap space 错误产生的常见原因可以分为以下几类:
1、请求创建一个超大对象,通常是一个大数组。
2、超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
3、过度使用终结器(Finalizer),该对象没有立即被 GC。
4、内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。
解决方案:
堆得参数调整;业务峰值采取限流措施;内存泄漏找到找到持有对象,修改代码,关闭资源
Permgen space
该错误表示永久代已用满,通常是因为加载的 class 数目太多或体积太大。
2)原因分析
永久代存储对象主要包括以下几类:
1、加载/缓存到内存中的 class 定义,包括类的名称,字段,方法和字节码;
2、常量池;
3、对象数组/类型数组所关联的 class;
解决方案:
根据 Permgen space 报错的时机,可以采用不同的解决方案,如下所示:
1、程序启动报错,修改 -XX:MaxPermSize 启动参数,调大永久代空间。
2、应用重新部署时报错,很可能是没有应用没有重启,导致加载了多份 class 信息,只需重启 JVM 即可解决。
3、运行时报错,应用程序可能会动态创建大量 class,而这些 class 的生命周期很短暂,但是 JVM 默认不会卸载 class,可以设置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC这两个参数允许 JVM 卸载 class。
Metaspace
JDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,通常是因为加载的 class 数目太多或体积太大。
此类问题的原因与解决方法跟 Permgenspace 非常类似,可以参考上文。需要特别注意的是调整 Metaspace 空间大小的启动参数为 -XX:MaxMetaspaceSize。
nable to create new native thread
每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误。
3)原因分析
JVM 向 OS 请求创建 native 线程失败,就会抛出 Unableto createnewnativethread,常见的原因包括以下几类:
1、线程数超过操作系统最大线程数 ulimit 限制;
2、线程数超过 kernel.pid_max(只能重启);
3、native 内存不足;
解决方案:
1、升级配置,为机器提供更多的内存;
2、降低 Java Heap Space 大小;
3、修复应用程序的线程泄漏问题;
4、限制线程池大小;
5、使用 -Xss 参数减少线程栈的大小;
整体分析:
因为服务器在运行过程中,堆空间不断地扩容与回缩,会形成不必要的系统压力 所以在线上生产环境中 JVM的Xms和 Xmx会设置成同样大小,
避免在GC 后调整堆大小时带来的额外压力。
堆内部分为新生代(Eden8/10,S01/10,S21/10) 1/3和老年代2/3,理想比例。执行命令查看JDK版本所有默认的JVM参数,可以调整新生代老年代的比例。
一般是数组引起的oom 在jdk的配置文件中可以配置打印oom的具体异常信息 方便查看
原空间 存放类和方法的元数据以及常量池 每当一个类初次被加载的时候,它的元数据都会放到永久代(有大小限制)中,类加载台多oom 在这里参数调节
jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码。
第一步先找出Java进程ID,服务器上的Java应用名称为mrf-center:
root@ubuntu:/#ps -ef | grep mrf-center | grep -v grep
得到进程ID号,第二步找出该进程内最耗费CPU的线程,可以使用
1)ps -Lfp pid
2)ps -mp pid -o THREAD, tid, time
3)top -Hp pid
用第三个,输出如下:
TIME列就是各个Java线程耗费的CPU时间,CPU时间最长的是线程ID为21742的线程,用

printf "%x\n" 21742

轮到jstack上场了,它用来输出进程21711的堆栈信息,然后根据线程ID的十六进制值grep,如下:

root@ubuntu:/# jstack 21711 | grep 54ee
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait()
可以看到CPU消耗在PollIntervalRetrySchedulerThread这个类的Object.wait()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值