JVM:吾就想清新吾是怎么没的

阅读: 作者:admin   发表于 2021-08-05 17:30

  

吾们都清新 Java 程序都是跑在 JVM 上的,一旦 JVM 有什么风吹草动,必然会影响服务的安详性。幸运的话,服务会发生抖动,能够有片面乞求展现迟误或变态。祸患的话,JVM 直接休业,导致服务十足休止。

这可不是什么益事,与 JVM 一首休业的,除了服务,还有吾们的心态。

所谓的 JVM 休业,清淡情况下就是指内存溢出,也就是 OutOfMemoryError 和 StackOverflowError。另外还有一栽情况就是堆外内存占用过大,这栽情况会导致 JVM 所在机器的内存被撑爆,从而导致机器重启等变态情况发生,吾们把这栽情况叫做内存泄露。

那什么情况下会造成 JVM 休业呢,有哪几栽类型的休业呢?俗语说,亲信知彼,方能百战不殆。晓畅了发生休业的因为,才能更益的解决 JVM 休业题目。

最先照样放出 JVM 内存模型图,JVM 要理解首来是很抽象的,借助下面这张图能够具象化的晓畅 JVM 内存模型,而发生溢出的几个片面都能够在图中找到。在 JDK 8 中,长期代已经不存在了,取而代之的是元空间(metaspace)。

下面就以 Hotspot JDK 8 为背景,望一下 JVM 内存溢出和内存泄露的几栽情况。

最先竖立 JVM 启动参数,限定堆空间大幼,堆空间竖立为 20M,其中重生代10M,元空间10M,并指定垃圾搜集算法采用 CMS 算法。之后的例子都会操纵这套参数。

-XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:+CMSClassUnloadingEnabled -XX:+ParallelRefProcEnabled -XX:+CMSScavengeBeforeRemark -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M -XX:HeapDumpPath=/Users/fengzheng/jvmlog 

堆溢出

堆溢出,答该是最常见的一栽内存溢出的场景了。JVM 平分配绝大无数对象实例和数组都存在堆上,另外堆内存也是垃圾搜集器做事的主要战场。

当吾们的 Java 程序启动的时候,会指定堆空间的大幼,新建对象和数组的时候会分配到堆上面,当新对象申请空间的时候,倘若堆内存不足了,就会发生垃圾搜集行为,大无数时候会发生在重生代,叫做 Minor GC。当重生代回收完善,空间照样不足的话,会发生一次 FullGC。FullGC 后,空间照样不足,此时就会发生 OOM 舛讹,也就是堆溢出。

模拟一下这个场景

private final static int _1K = 1024;  public static void main(String[] args){   List<byte[]> byteList = new ArrayList<>();   quietlyWaitingForCrashHeap(byteList); }  public static void quietlyWaitingForCrashHeap(List<byte[]> byteList) {   try {     while (true) {       byteList.add(new byte[500 * _1K]);       //Thread.sleep(1000);       Thread.sleep(100);     }   } catch (InterruptedException e) {    } } 

上面的手段会不息的向List

下面是程序运走之后的效果,经过垃圾回收最后照样异国有余的空间,从而发生 java.lang.OutOfMemoryError: Java heap space变态。

image-20201016211017630

发生堆内存溢出的根本因为就是操纵中的对象大幼超过了堆内存大幼。

堆内存空间竖立的太幼,要按照预估的实际操纵堆大幼相符理的竖立堆空间竖立。

程序有漏洞导致,某些静态变量不息的添大,例如缓存数据舛讹的初首化,导致缓存无终点的增补,最后导致堆内存溢出。针对这栽情况,恐怕没什么益手段,除了做益测试之外,就是在题目发生后做益日志分析。

栈溢出

虚拟机栈是用来存储片面变量外、操作数栈、动态链接、手段出口等新闻的,每调用一个 Java 手段就会为此手段在虚拟机栈中生成栈帧。

栈除了包括虚拟机栈之外,还包括本地手段栈,当调用的手段是本地手段(例如 C 说话实现的手段)时,会用到本地手段栈。不过,在 HotSpot 虚拟机中,虚拟机栈和本地手段栈被相符二为一了。

模拟栈溢出场景

public static void main(String[] args){   stackOverflow(); }  /** * stackoverflow */ public static void stackOverflow() {   stackOverflow(); } 

在上面的代码中,stackOverflow() 手段的调用是一个无限递归的过程,异国递归出口。前线说了,每调用一个手段就会在虚拟机栈中生成栈帧,无限的递归,必定造成无限的生成栈帧,末了导致栈空间被填满,从而发生溢出。

image-20201019122447325

上面模拟了最常见的一栽状况,产生这栽状况的因为很能够是因为程序 bug 导致的,清淡来说,递归必定会有递归出口,倘若因为某些因为导致了程序在实走的过程中无法达到出口条件,那就会造成这栽变态。还有就是循环体,循环体的循环次数倘若过大,也有能够展现栈溢出。

另外还能够是其他比较不容易展现的因为,比如创建的线程数过多,线程创建要在虚拟机栈平分配空间,倘若创建线程过多,能够会展现 OutOfMemoryError变态,但是清淡来说,都会用线程池的手段代替手动创建线程的手段,因而,这栽情况不容易展现。

元空间溢出用于存储已被虚拟机添载的类新闻,常量,静态变量,即时编译(JIT)后的代码等数据,在 JDK 8 中,已经用 metaSpace 代替了长期代的。默认情况下 metaSpace 的大幼是异国限定的,也就是所在服务器的实际内存大幼,但是,清淡情况下,最益照样竖立元空间的大幼。

清淡在产生大量动态生成类的情景中,能够会展现元空间的内存溢出。

模拟元空间溢出

public static void main(String[] args){   List<byte[]> byteList = new ArrayList<>();   //quietlyWaitingForCrashHeap(byteList);   // stackOverflow();   methodAreaOverflow(); }  public static void methodAreaOverflow() {   int i = 0;   while (true) {     Enhancer enhancer = new Enhancer();     enhancer.setUseCache(false);     enhancer.setSuperclass(MethodOverflow.class);     enhancer.setCallback(new MethodInterceptor() {       @Override       public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {         return methodProxy.invokeSuper(o, objects);       }     });     enhancer.create();     System.out.println(++i);   } } 

始末 CGLIB 的手段动态的创建许多个动态类,云云一来,类新闻就会越来越多的存到元空间,从而导致元空间溢出。

image-20201019163227576

例如在操纵 Spring、 MyBatis 等技术框架的时候会动态创建 Bean 实例类,另外,Spring AOP 也会产生动态代理类。

堆外内存溢出

大无数情况下,内存都会在 JVM 堆内存平分配,很少情况下必要直接在堆外分配内存空间。操纵堆外内存的几个益处是:

在进程间能够共享,缩短虚拟机间的复制 对垃圾回收停留的改善:倘若行使某些长期存活并大量存在的对象,往往会触发YGC或者FullGC,能够考虑把这些对象放到堆外。过大的堆会影响Java行使的性能。倘若操纵堆外内存的话,堆外内存是直批准操作体系管理( 而不是虚拟机 )。云云做的效果就是能保持一个较幼的堆内内存,以缩短垃圾搜集对行使的影响。 在某些场景下能够升迁程序I/O操纵的性能。少往了将数据从堆内内存拷贝到堆外内存的步骤。

清淡在必要大量频频的进走 IO 操作的时候会用到堆外内存,例如 Netty、RocketMQ 等操纵到了堆外内存,主意就是为了添迅速度。

因而,在展现体系内存占用过大的情况时,排查堆栈无果后,能够望一下堆外内存的操纵情况,望望是不是堆外内存溢出了。

总结

事前做益配置

JVM 题目自己就是比较抽象和难以直不悦目发现的,因而在项现在上线前除了做益代码逻辑的测试外,还要对 JVM 参数进走相符理配置,按照行使程序的体量和特点选择益正当的参数,比如堆栈大幼、垃圾搜集器栽类等等。

另外,垃圾搜集日志肯定要有保留,还有就是发生内存溢出时要保存 dump 文件。

事中做益监控

在程序上线运走的过程中,做益 JVM 的监控做事,比如用 Spring Admin 这栽比较轻量的监控工具,或者大型项现在用 Cat、SkyWallking 等这些分布式链路监控体系。

过后做益现场珍惜和分析

再相符理的参数配置和监控平台,也不免不发生变态,这也是很平常的,不展现变态才有题目益吧。在发生变态之后,要及时的保留现场,倘若是多实例行使,能够一时将发生变态的实例做下线处理,然后再进走题目的排查。倘若是单实例的服务,那要及时实在认最新的日志和dump已经留存益,确认完善后,再采取舛讹让服务重启。

本文转载自微信公多号「古时的风筝」,能够始末以下二维码关注。转载本文请有关古时的风筝公多号。

【编辑选举】

亿级流量体系如何玩转 JVM 技术干货:JVM架构体系与GC命令全梳理,提出珍藏 秒懂JVM的三大参数类型,就靠这十个幼实验了 JVM:可视化 JVM 故障处理工具 JVM系列—Class文件添载过程


Powered by 彩票9.99平台-彩票9.99倍平台 @2018 RSS地图 HTML地图

导航

热点推荐

最新发布

友情链接