当前位置: 首页 > 科技观察

Java内存错误?就因为你不够帅!_0

时间:2023-03-16 22:35:42 科技观察

从小就对Java有着很深的感情,有几十年的Java经验。那时,Java仍归Sun所有。我有多年的Servlet经验和CURD经验。现在我已经自我革新,转而研究人生哲学。算了,别吹了。这篇文章是关于Java故障排除的,属于上一篇文章。为了保证文章的流畅性,我决定一口气看完。因为有很多相关方面的培训,所以写的时候不需要参考资料和翻源码。算下来,这篇文章没用一个小时,但篇幅已经很长了。如果它长大了,就把它剪掉。本文定为内存调查第一篇,主要讲一些原理。为什么要讲原理?你需要了解汽车的结构才能驾驶吗?这真是无与伦比。汽车很少发生故障。万一出了问题,就要花钱找拖车公司和4S店。您还将每年为其购买保险。反观Java,三天两头出问题,你也找不到人解决,给钱也不一定能解决问题。可以比较吗?盘点盘点,最后只能靠自己了。内存里有什么操作系统内存JVM内存分区图解说一切烦恼,jvm内存从未如此简单!为什么会出现内存问题垃圾回收器重要概念GCRoots对象改进1.内存问题,我们要看内存里有什么。我们先看操作系统内存的划分,再看JVM内存的划分。由于JVM本身作为一个普通的应用程序运行在操作系统上,其行为也受到操作系统的限制。2.操作系统内存我们先从操作系统的实现说起。通常,我们写一个C语言程序。编译后我们会发现里面的内存地址是固定的。其实我们的应用程序编译之后,这些地址都是虚拟地址。它需要经过一层翻译才能映射到真正的物理内存。MMU是负责地址转换的硬件。那么我们操作系统的可用内存是多少呢?它实际上分为两部分。一部分是物理内存,指的是我们插入的内存条;另一部分是磁盘模拟的虚拟内存,在Linux中通常称为swap分区。所以,可用内存=物理内存+虚拟内存。如果您的系统启用了交换,则可用内存大于物理内存。top命令和free命令都可以用来查看内存使用情况。top命令可以看到各个进程的内存使用情况。我们通常会关注RES这一列,它代表了进程的实际内存使用情况。我们在搭建监控系统的时候,通常会监控这个值。我们再来看看free命令的显示。它的显示其实有点混乱,具体关系可以看上图。通常free显示的数值比较小,但这并不代表系统的可用内存就那么一点点。Linux操作系统启动后,随着机器的运行,剩余的内存会很快被buffer和caches等缓冲区和缓存占用,当应用程序的内存空间不足时可以释放这些内存。可用内存=空闲+缓冲区+缓存。通过/proc/meminfo可以查看各个区域具体的内存使用情况。#cat/proc/meminfoMemTotal:3881692kBMemFree:249248kBMemAvailable:1510048kBBuffers:92384kBCached:1340716kB40+more...三、JVM内存划分接下来我们看一下JVM内存区域划分。在JVM中,最大的内存区域就是堆,我们平时创建的大部分对象都会存放在这里。所谓垃圾回收,主要是针对这部分的。许多JVM书籍描述:在JVM中,除了程序计数器外,其他区域都可能溢出。我们在这里仍然同意这个结论。下面只是对这些内存区域做一个简单的介绍,因为有些知识对于我们的内存考察是没有好处的。堆:JVM堆中的数据是共享的,占用的内存面积最大。虚拟机栈:Java虚拟机栈,是基于线程的,用于服务字节码指令的运行程序计数器:当前线程执行的字节码行号指示器Metaspace:方法区在这里,非堆本地内存:其他内存footprints类比上图,我们可以回到一些常用对象的分配位置。不用担心栈上的分配逃逸分析,不用关注栈帧和操作数栈的双层结构。这些小细节对物体的海洋影响太小了。我们关注的内存区域其实只有两个概念:堆内内存和堆外内存。4.一张图解万千烦恼,jvm内存从未如此简单!5、为什么会出现内存问题统计显示,在我们平时的工作中,OOM/ML问题约占5%,但平均处理时间达到40天左右。可见,这类问题的排查难度很大。但让人无语的是,在遇到内存问题时,工程师的现场保护意识往往不够,尤其是不够。只知道内存溢出的结果,却一无所获。没有监控,没有日志,甚至不清楚它是什么时候发生的。这样的问题,鬼知道原因。6.垃圾收集器内存问题有两种模式,一种是内存溢出,一种是内存泄漏。内存溢出OutOfMemoryError,简称OOM,堆是最常见的情况,堆外内存很难排查。MemoryLeak内存泄漏,简称ML,主要是指分配的内存没有释放。内存一直在增长,有OOM风险;回收的内存在GC时无法回收;或者它可以被回收但很快就会变满,从而产生压力。内存问题的影响也非常大,比如下面三种场景。OOMError发生,应用停止(最严重)GC频繁,GC时间长,GC线程时间片占用多服务卡顿,请求响应时间变长说到这个卡顿问题,不得不提垃圾集电极。很多同学看到上图就知道我们要说的是G1垃圾收集器,这也是我的推荐。CMS等垃圾收集器的回收时间不可控。如果你有条件,当然应该避免使用它们。CMS也将在Java14中被移除。我真的不希望你掌握一些即将过时的经验。ZGC虽然强大,但还是太新了,几乎没人敢吃螃蟹,剩下的就是G1了。通过三个简单的配置参数,G1在大多数情况下都能获得出色的性能,工程师们也开心多了。三个参数如下:MaxGCPauseMillis预定目标,自动调整。G1HeapRegionSize小堆区域大小。InitiatingHeapOccupancyPercent堆内存比例阈值,开始并发标记。如果你还在着急,想了解G1的原理,那我们也可以提一下。G1其实还是有年轻代和老年代的概念的,只是它的内存不是连续的。如图所示,G1将内存划分为大小相等的区域。这些区域称为小堆区域,是垃圾回收的最小单位。以前的垃圾收集器采用整代收集,而G1是部分收集,因此可以根据配置的最小延迟时间合理选择小堆区域的数量,收集过程变得更加智能。7.重要概念GCRoots如图所示,要判断哪些是垃圾,需要有一种寻找垃圾的方法。其实我们上一句的说法是不正确的。在JVM中,找垃圾的方法和我们理解的正好相反:先找到存活对象,标记存活对象,然后一口气回收其他对象。在垃圾回收期间,JVM关心的不是回收不是垃圾的对象,而是清理垃圾对象。要找出哪些是幸存的对象,需要从源头追溯。在JVM中,常见的GCRoots都有静态的成员变量,比如静态的HashMap。另一部分是与线程关联的虚拟机栈和本地方法栈的内容。我们说了很久,其实这种回溯的方法有一个专有名词:可达性分析法。还有一种类似的引用计数方式,但是由于循环依赖的问题,几乎没有收集器使用这种形式。并不是说只要和GCRoots有联系(ReferenceChain),这个对象就是活着的,这也跟对象的引用级别有关。强引用:是最常见也是最强的一种存在。只有与GCRoots断绝关系才会被淘汰。软引用:只有当内存不足时,系统才会回收软引用对象。弱引用:当JVM垃圾回收时,无论内存是否充足,弱引用关联的对象都会被回收。幻影引用:幻影引用主要用于跟踪被垃圾收集的对象的活动。通常,我们使用的对象是强引用。软引用和弱引用在一些缓存框架中被广泛使用,对象的重要性相对较弱。8.对象提升大多数垃圾收集器都是分代垃圾收集,我们从上面对G1的描述可以看出。如图所示,是一个典型的分代恢复内存模型。对象从年轻代晋升到老年代有四种方式。定期推广,对象够老。比如从from到to转了15次之后,还没有被回收。控制参数是-XX:MaxTenuringThreshold。这个值在CMS下默认为6,在G1下默认为15。AllocationGuaranteeSurvivor空间不够,老年代保证。大对象直接分配在老年代。动态对象年龄确定。比如G1中的TenuringThreshold会随着堆中对象的分布而变化。垃圾收集器的优化是保证尽可能多的对象分配在新生代,减少对象被提升到老年代的可能性。虽然这种思路在G1中被削弱了很多。End了解了操作系统的内存中有什么,JVM的内存中有什么,这样我们就可以冷静而放纵地针对每一种问题情况进行有针对性的排查和优化。文章到此为止。在下一篇文章中,我们将通过几个实际案例来了解一下排查Java内存问题的具体过程。