Java程序员也应该知道的系统知识系列之内存

简介:

作者:林昊

neicun

上篇说到了Java程序和CPU的关系,对于多数实现的较好的Java应用程序而言,基本上随着CPU的核数增加或能力提升,系统能够支撑的并发量就可以稳步上升,但对于内存而言,是否也是这样呢,这篇我们就来看看Java程序和内存的关系。

 

和CPU一样,我们首先要知道机器上的内存的硬件状况,在linux下,可以通过dmidecode | grep -A16 “Memory Device$”命令来查看机器插了多少根内存条,以及每根内存条的具体型号,内存条的具体型号对Java应用的运行性能也会有些影响,但一般来说不会有CPU那么明显。

要查看机器上内存的使用状况,可通过free -m来查看,这个时候常见的第一个问题是看到free值很小,就认为内存不够用了,但其实真正可用的内存是free+buffers+cached,os为了提升运行性能,会利用一些内存来做cache,以提升诸如读写文件的速度等。

 

当free不够的时候,os会根据一个系统值来决定是释放buffers/cached还是使用swap,如果swap没开启就不用判断了,如果swap开启了,那么vm.swappiness这个值就非常关键了,这个值是一个倾向值的意思,值越大表示越倾向于使用swap,越小表示越倾向于释放buffers/cached,对于响应时间敏感的应用而言,只要用到swap了,通常对响应时间的影响都会很明显,而且swappiness默认是60,意味着默认其实是倾向于使用swap的,因此对于这类系统建议最好是关闭swap,毕竟对于集群型的应用来说,通常都是宁可接受内存不够用的情况下机器挂掉,也不能接受响应时间变慢。

 

对于cached的内存区域,可以执行echo 3 > /proc/sys/vm/drop_caches来强制释放,这种在某些情况下可能会需要用,例如希望把还在cache里的文件内容刷到磁盘。

 

对于swap区域,可以通过执行swapoff -a来强制刷掉,如果需要再开启,可以执行swapon -a。

 

除了os利用内存来提升运行性能外,cpu也同样借助它的各级cache来提升运行速度,多核之后,UMA的方式导致系统总线带宽会比较吃紧,而NUMA是解决这个的一种好的方式,关于NUMA具体是什么就不在这里讲了,需要知道下的是默认通常是不打开NUMA的,从我们的一些测试来看,有些CPU型号在是否打开NUMA的情况下应用的性能会相差一倍,不过大部分的CPU型号里打开NUMA的提升大概会在20%–30%左右,如果OS没打开NUMA,其实在Java启动参数上设置了-XX:+UseNuma也是没什么用的,可以用numactl -H来查看NUMA是否打开,但由于打开NUMA的话对应用跑在同一个NUMA Node上要求还是比较高的,因此在虚拟机类的场景中为了追求CPU搭配的灵活性以及维护的简便性,通常就只能放弃NUMA了。

 

要看运行的Java进程消耗的内存,可以用ps aux | grep java或具体的pid、或top -p [pid]也可以看,可以看到的是有两列内存的信息,一列是VIRT,一列是RES。

 

VIRT表示的是此进程占用的地址空间的大小,地址空间在32bit的os上的上限是3G,在64bit可以认为是无限大,当地址空间不够用的时候,Java进程会直接crash,在crash的log里会有java.lang.OutOfMemoryError: Out of swap space的信息,Java进程在启动时会根据-Xms + -XX:PermSize先申请好相应大小的地址空间,在创建线程等的时候也会直接申请好-Xss对应大小的地址空间,所以创建了很多线程的情况下可以看到VIRT会很高,

 

RES表示的是此进程具体占用的内存的大小,这个地方很容易产生两个疑问:
1. 为什么看到的RES值大于或小于了-Xmx的设置;
Java应用在刚启动,或者说还没有到触发Full GC之前,只有当真正需要使用内存才会去占用实际的内存,否则只是占据了地址空间,因此看到的RES值有可能会小于-Xmx的值;
而对于一个运行了一段时间且触发过CMS GC/Full GC的Java应用而言,则很有可能看到的RES大于了-Xmx的值,原因在于Java除了-Xmx会占用相应的内存外,Perm Gen、C Heap(CodeCache、Direct Memory、线程、对象结构、GC等)也要占据一些内存,所以看到的RES大于-Xmx也很正常。

 

2. 为什么GC后RES的值没下降相应的数值;
这个的原因在于GC后JVM并不会把内存释放给OS,而是会占着继续用。

 

Java程序在运行中过程,除了Direct Memory、直接用Unsafe操作、或间接的使用Deflater等的会涉及到C Heap,更多的是去JVM Heap中申请内存,并且由于JVM包装掉了,所以Java程序员在写代码的时候很容易由于错误的使用API或数据结构导致内存的浪费,这通常是为什么很多C的高手(注意:这里说的是C的高手)写的代码效率会比普通的Java程序员写的高不少的一个原因之一,而回收也由JVM来控制,这个系列的文章主要是科普下系统方面的知识,JVM的一些就不在这里写了,在之前的一些PPT或文章里也写过很多次关于JVM的内存管理,同样关于怎么去查Java程序在JVM Heap和C Heap里的消耗,之前也写过不少的文章,就不在这里写了,毕竟这些多数和系统关系就不算大了。

 

关于内存资源这块,Java程序倒不一定是越多越好,内存越大,通常也就意味着GC的负担越重,而GC的时候通常应用是全暂停的(除了CMS是Almost Concurrently外),但也不能太小,太小的话运行时会比较明显的暴露出来,因为会导致非常频繁的GC(到底多频繁算频繁呢,从目前的经验来看,ygc尽可能能在3s+一次,fgc或cms gc的话最好在10分钟以上),而太频繁的GC会导致CPU大部分时候都耗了执行GC上,应用能够支撑的并发量自然就会不够,够用就OK,在排除内存泄露等因素外,可以看看在Full GC后实际需要占用的内存大小,一般来说只要确保给Java进程留有的空间比这个需要常驻的大小大一定比例就OK(不过到底大多少还真不好说,凭经验吧),不要因为机器内存有多(相对而言,现在多数机器在内存这块都是比较够的),就给Java分配更多的内存,否则一次较长时间的暂停搞不好就回导致极大的杯具,所以内存资源这块和CPU不太一样,我的观点一向是够用并留有一定空间就OK,而不用去追求用满,当然如果能充分有效的利用多余的内存提升性能当然是OK的,例如cache什么的。

 

从内存资源的状况可以看到,随着硬件的不断发展,将来对Java应用而言,会有个悲催的现象是,CPU用的比较满,但机器的内存资源浪费的比较严重,针对这个问题,看来后面必须专门写一篇来讲讲虚拟化。

 

说到这了,顺带说下上篇文章留下的一个话题,就是GC这种线程在执行的时候是怎么确保占有足够的时间片,这个的原因是GC在执行的时候其他的线程其实都是处于暂停状态(其实这话不太准确),GC要执行前,JVM会先将一个内存页设为只读,而在所有有引用关系赋值的地方,JVM在编译代码时都会先插入一个检查某个内存页的状态的代码,而因为之前GC已经把这个内存页状态设为了只读,所以当其他线程的代码走到这个地方的时候,会抛出异常,从而导致线程进入一个blocked的状态,就不会来抢占GC线程需要的CPU了。

相关文章
|
2月前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
51 6
|
1月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
40 0
|
2月前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
2月前
|
监控 Java API
如何使用Java语言快速开发一套智慧工地系统
使用Java开发智慧工地系统,采用Spring Cloud微服务架构和前后端分离设计,结合MySQL、MongoDB数据库及RESTful API,集成人脸识别、视频监控、设备与环境监测等功能模块,运用Spark/Flink处理大数据,ECharts/AntV G2实现数据可视化,确保系统安全与性能,采用敏捷开发模式,提供详尽文档与用户培训,支持云部署与容器化管理,快速构建高效、灵活的智慧工地解决方案。
|
9天前
|
存储 分布式计算 Hadoop
基于Java的Hadoop文件处理系统:高效分布式数据解析与存储
本文介绍了如何借鉴Hadoop的设计思想,使用Java实现其核心功能MapReduce,解决海量数据处理问题。通过类比图书馆管理系统,详细解释了Hadoop的两大组件:HDFS(分布式文件系统)和MapReduce(分布式计算模型)。具体实现了单词统计任务,并扩展支持CSV和JSON格式的数据解析。为了提升性能,引入了Combiner减少中间数据传输,以及自定义Partitioner解决数据倾斜问题。最后总结了Hadoop在大数据处理中的重要性,鼓励Java开发者学习Hadoop以拓展技术边界。
34 7
|
2月前
|
缓存 Java Linux
如何解决 Linux 系统中内存使用量耗尽的问题?
如何解决 Linux 系统中内存使用量耗尽的问题?
229 48
|
1月前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
48 8
|
1月前
|
机器学习/深度学习 人工智能 缓存
【AI系统】推理内存布局
本文介绍了CPU和GPU的基础内存知识,NCHWX内存排布格式,以及MNN推理引擎如何通过数据内存重新排布进行内核优化,特别是针对WinoGrad卷积计算的优化方法,通过NC4HW4数据格式重排,有效利用了SIMD指令集特性,减少了cache miss,提高了计算效率。
57 3
|
1月前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。