Java生产填坑经历之全面回忆
生产填坑的经历为什么要称之为全面回忆呢,因为恰巧笔者之前看了一部科幻电影,名字就叫《全面回忆》 Total Recall,该片于2012年10月20日在中国上映,豆瓣评分7.0。
全面回忆的剧情简介......
欢迎来到Rekall,它是一个能够把你的梦境变成现实的工厂。主人公道格拉斯·奎德(柯林·法瑞尔 Colin Farrell 饰)是一名普通的工厂工人。尽管他有一位漂亮的妻子(凯特·贝金赛尔 Kate Beckinsale 饰),两人看似恩爱,但他内心似乎依然不满足。思绪旅行听起来像是个完美的假期,让他从沮丧的生活中得到放松——作为一名特工的真实记忆可能正是他所需要的。但是当这个过程出现可怕的异常时,奎德变成了一个被追杀的逃犯。他发现自己正在躲避受考哈根长官(布莱恩·科兰斯顿 Bryan Cranston 饰)控制的警察,没有一个人是奎德可以信任的,除了一名叛军的女战士(杰西卡·贝尔 Jessica Biel 饰),她为地下抵抗组织的首领(比尔·奈伊 Bill Nighy 饰)工作。幻想和现实之间的界线变得模糊,他的命运凶吉难卜,因为奎德发现了他的真实身份,他的真爱,还有他真正的命运。
本片改编自菲利普·K·迪克的著名短篇小说《We Can Remember It For You Wholesale》。
说来也巧,笔者认为大多数的生产填坑经历就像这部电影的剧情一般,男主角在无数次的美好记忆转换过程中,由于程序出现了一个可怕的错误导致了很多无法预期的可怕事件发生,只是在生产填坑中这个男主角可能是程序员。
内存全面回忆
那么对于一个Java程序开发者经常会遇到哪些坑呢?笔者仔细全面回忆了一番自己踩过的坑,真的是数不胜数,苦不堪言呐,所以笔者认为有必要做一些简单的总结与反思。针对Java应用程序的生产故障,笔者认为可以归纳为如下几个方面,一是内存方面,二是高并发导致的数据不一致,三是其他方面包括但不限于数据库服务故障、网络故障、磁盘故障等。以上几个方面,内存方面是最常见也是最重要的,可以说笔者遇到过的生产坑有80%都是内存问题,java语言实现了内存自动回收可谓说是一大技术进步,但同时这也成了悬在每个java程序员头顶的一只靴子,指不定什么时候会掉下来把你砸吐血。关于内存的垃圾自动回收在《深入理解JVM虚拟机原理》一书中有一句话讲的特别经典:
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的”高墙“,墙外面的人想进去,墙里面的人却想出来
首先生产内存问题按解决问题的时间复杂度和精力成本从低到高依次为JVM内存不足、JVM内存泄漏、高并发下的FullGC这三座大山
- JVM内存不足这类问题比较简单,主要原因是由于JVM向操作系统申请不到新的可用内存导致,一般常常表现为Java应用程序在运行过程中经常会自动重启,导致业务服务暂时中断。一般可查看GC日志,结合业务量和主机实际物理内存来重新设定JVM最大堆内存,或者直接增加主机的物理内存。这类问题笔者一般倾向于直接加大主机的物理内存,因为相较而言增加硬件配置在一定程度上来说反而是付出成本最低的。(有这调优的精力,程序员可以去做更多其他有意义的事情,创造更多的价值,现在硬件加个内存成本真的挺低的)
- JVM内存泄漏这类问题笔者认为是生产最常见的一个问题,也是发生概率最大的。主要原因是这类问题大多是由于Java程序员自己埋下的坑导致,如应用代码不停的new新对象内存又从不释放、往List或者Map中一直添加对象又不清理、在大量循环遍历代码块中操作字符串直接定义变量相+等等之类。近来笔者刚处理了一个内存泄漏的问题,是一个已上线运行了好几年的业务系统,每天上班工作时间大概会有40-50号人登陆系统进行录入作业,用户都是外包录入人员,每天按录入任务数计件算绩效。某一天突然就接到外包录入人员反馈说系统这段时间时不时变的好卡,具体表现在获取录入任务和提交任务时经常出现卡顿。最开始笔者判断可能是不是最近业务量增长了很多导致的,因为通常情况下已经稳定运行了好几年的系统一般不会有内存泄漏的问题,所以一开始笔者通过远程查看应用日志和统计数据库记录来确认在最近时间段内业务量是不是有所增长,但发现最近的业务量比上年度反而下降了许多,这就奇怪了,于是笔者再跑去终端用户的客户端浏览器NetWork中抓取每个获取任务和提交任务的action响应时间,发现每固定间隔一段时间就会接连遇到提交任务action服务端响应时间耗时超过3~5秒的异常情形(正常的响应时间大都是百毫秒级别),且这段时间的流量是平稳的,由此来看不太像是高并发,那么笔者就又把问题原因怀疑上了内存方面,于是登陆应用服务器,使用命令kill -3 pid 查看生成的javacore文件发现此时JVM堆内存已几乎耗尽至能申请到的配置最大4G内存,同时也发现在应用程序bin目录下已生成Java HeapDump文件,于是笔者进一步使用工具分析Dump文件发现90%的内存都被一个HashMap使用掉了,于是经过结合项目源码找到了这个Map,最终发现写这个代码的程序员忘了即时清理Map缓存。因为每次put map中的数据量比较小,之前系统未遇到问题是由于项目之初生产运维经常有切换演练或者定期软件更新,应用经常会重启释放内存,这次是1年多来应用程序都没有重启过,于是这个问题就暴露出来了,最后增加map的清理机制,此问题得以解决。针对这类问题最麻烦的是要找到内存泄漏的点,这里就要用到”工欲善其事必先利其器“的方法论了。对于HotSpot虚拟机我们应该尽可能多的去了解JVM内存诊断的辅助工具,如可视化多合一故障分析监控诊断工具VisualVM、jstat虚拟机内存统计信息监视工具、jmap内存映像工具、jhat虚拟机堆转储快照分析工具等,如果是非Sun系列,比如IBM的j9vm,可了解HeapAnalyzer、Javacore Analyzer、Garbage Collector Analyzer工具。由于笔者能力知识范围有限目前接触过和解决过生产问题的项目只用到过以上2个系列的JVM,对于其他平台,如HP VM、JRockit VM只是知道有这些个东西存在。
- 高并发下的Full GC生产故障通常存在于追求高吞吐量和低停顿的高质量服务系统中。想象一下在高并发的场景下,JVM 发生Full GC回收垃圾,导致所有正在执行业务的处理线程突然全部跑到安全点之上卡住停顿下来,对业务来说这将是一场怎样的灾难。以上过程简称STW(Stop The World)。解决Full GC引起的STW需要Java开发者对JVM有着很深入的理解,了解JVM各项配置参数,了解垃圾回收器主流的收集器算法,如ParNew、Parallel Scavenge/Old、Serial、CMS、G1等收集器算法,同时能理解和分析GC日志,在高并发业务应用场景下选择最适合自身应用的垃圾收集器算法组合和最优JVM配置参数,使应用Full GC发生的频率尽可能的降到最低同时使单次Full GC的时间尽可能的短。这一块需要通过大量的性能测试和GC日志跟踪监控持续调优,才能优化虚拟机的内存使用率,解决这类问题知识、经验、数据支撑是关键。以上是笔者处理Java内存方面问题的一些心得体会及感受,因为体验过很多次切肤之痛,所以有些感触还是挺深。
其次关于高并发导致的数据不一致问题这次就先不谈,篇幅有限,以后再说吧,笔者也是能力有限,需要再积累下。不过笔者认为对于我们Java程序员来说,并发编程是技术含量最高的一部分,这块很难单独讲完,暂时想到的内容主要包括Java内存模型、主内存与工作内存、线程间内存交互共享、原子性、可见性与有序性、先行发生原则,锁机制等等以上每一项内容都至少可以花一篇文章的篇幅来讲吧。
最后再说下其他方面的问题吧,这里分享一个大概2个月前遇到的生产应用数据库CPU使用率突然100%问题解决案例吧。简单交代下业务系统的背景,线上运行的是一个自助交易类系统,目前的日交易流水在40W笔左右,当然终端用户数还在扩充,预计未来日交易流水在百万级别,数据库采用的是DB2,服务器配置为一台4C 16G内存的小机,平时交易繁忙期CPU利用率也不会超过20%,很难想象一个日交易不过百万流水的应用系统突然就能把4核的CPU吃完(最近也无什么大更新),通过机房运维监控,场外求助数据库专家,使用db2top命令查询数据库每个applicaion id 执行的sql发现是由于某一个sql执行效率异常低下形成慢查询导致,当即抓到这个sql,发现这个sql是个关联表查询,sql写法异常低级,存在not in中拼接大量排除条件。庆幸的是这涉及一个业务管理端的非关键功能,平时都没什么人会用这个功能,只是刚好这次业务上有个通知,导致这块功能同时使用的人数较多。为防止数据库CPU资源不足的情况下造成大量数据库正常操作读写超时影响正常交易,情节严重甚至可能发生数据库服务器宕机的风险,笔者当即采取了功能降级的操作,通过临时更改用户菜单权限,将该功能菜单暂时对用户屏蔽以解燃眉之急。后面再通过优化程序sql,对涉及业务功能做性能测试,验证通过之后再重新开放给用户,最终问题得以解决。对于其他方面的问题解决之道,笔者也没有很好的经验,总结起来就是开发人员需要尽可能的拓展自己的知识面,比如数据库、存储、网络等方面,很多时候你一头雾水,茫然不知所措,是源于自己对陌生事物的无知,解决这类问题知识储备是你最有力的武器。
填坑总结
工作这么多年以来生产填坑是真不少,上面列举的只是最近一年内遇到的,对于每次复盘处理之后的生产故障问题,笔者也是会经常反思一下,故而今天在这里总结一下遇事处理原则:
- 生产无小事原则,往大了说生产事故涉及到软件用户的根本利益,往小说一次严重的生产事故可能导致公司或者企业失信破产,员工失业。
- 弃卒保车原则,在尽可能多的熟悉和了解生产环境之后评估当前问题对整个系统运行影响面有多大,快速制定应急响应机制,如采取限流、降级、熔断等技术手段减少应用故障损伤面。
- 服务不重启原则即不到万不得已的情况之下不要想着重启服务,笔者也是遇到过生产故障到了山穷水尽的地步,没办法只能重启服务,但过来经验告诉我能不重启服务就尽量不要重启,原因在于重启之后可能故障依然存在,同时可能还会造成很多异常数据形成故障二次伤害。真要重启服务的话,应该详尽考虑有效安全的重启机制,尽可能的利用外部资源来配合,如通过主备切换流量控制逐台重启、负载均衡流量控制重启等手段尽可能的减少因重启造成的业务影响。
- 好心态原则,面对生产故障,开发人员应该要有一个积极良好的心态,你上来是来解决问题的,不要甩锅,不要抱怨,更不要BB,思想上不懈怠,大脑保持冷静,不慌不乱,积极沟通,了解情况,收集数据,制定最优应对之策。
- 全面复盘原则,对于生产应用故障原因一定要有追根结底的决心,做到及时复盘反思,杜绝类似问题再度发生。
写在最后
以上算是一遍填坑经历软文吧,任何应用都可能会发生生产故障,有软件原因也有硬件原因,有外部因素也有内部因素,当面对应用故障拦路虎时我们唯有迎难而上,拥有一个好心态是关键。计算机领域的问题都是有迹可寻的,我们能遇到的大多数问题都必定会留下蛛丝马迹,只要你肯花心思和精力去探寻,必定能找到你想要的答案,真的实在不行也会有替代方案,无非就是代价问题。
本文一些观点只是本人拙见,不一定都是对的,关于如何填坑解决问题大家有什么建议或任何疑问都可直接评论或者简信我,可共同探讨交流。
码字不易,如果你觉得本文对你的工作或者生活有帮助,正好你又比较慷慨,可以给个赞赏不在乎多少或者点个️。平时笔者有稳定的工作收入,写作只是用来学习和记录生活,非职业,但你的点赞会让我知道自己写的文章是否真的对du'zhe有用,这会给予我继续写作下去的动力,谢谢!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
java序列化
# 背景 java对象是在jvm中,如果jvm销毁,那么对象都不存在了。如果想继续使用java对象的话,需要用到序列化,将java中的对象转化为字节序列,用于存储和运输; 那么可以将DB理解为一种序列化,将java对象序列化后存储在DB中,将java对象保存在文本中也是一种序列化 # 细节 需要被序列化的类,需要实现Serializable接口 虽然Serializable接口是空的,没有任何方法,但也要实现,起到标识的作用 同一字节流中的引用是得到保存的 User user = new User("jwen"); Order o1 = new Order(user, "o1"); Order o2 = new Order(user, "o2"); 将这两个对象o1和o2序列化保存在同一文本中(同一输出流),会发现两个对象的o1.user == o2.user 但将两个对象分别保存在不同文件中,file1和file2的话,引用是不一样的 o1.user != o2.user 反序列化没有调用构造函数 public class User implements Serializabl...
- 下一篇
Java反射详细介绍
反射 目录介绍 1.反射概述 1.1 反射概述 1.2 获取class文件对象的三种方式 1.3 反射常用的方法介绍 1.4 反射的定义 1.5 反射的组成 1.6 反射的作用有哪些 2.反射的相关使用 2.1.1 通过反射获取无参构造方法并使用 2.1.2 通过反射获取带参构造方法并使用 2.1.3 通过反射获取私有构造方法并使用 2.1.4 通过反射获取成员变量并使用 2.1.5 通过反射获取无参无返回值成员方法并使用 2.1.6 通过反射获取带参无返回值成员方法并使用 2.1.7 通过反射获取带参带返回值成员方法并使用 2.1.8 通过反射获取无参带返回值成员方法并使用 3.相关知识点 3.1.1 设置setAccessible(true)暴力访问权限 3.1.2 获取Filed两个方法的区别 3.1.3 获取Field的类型 3.1.4 Method获取方法名,获取方法参数 3.1.5 Method方法的invoke()方法执行 关于链接 1.技术博客汇总 2.开源项目汇总 3.生活博客汇总 4.喜马拉雅音频汇总 5.程序员聊天笔记汇总 5.其他汇总 0.问题答疑 0.1 被反...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题