可能很多人都知道Java程序上生产后,运维人员都会设定好JVM的堆大小,而且还是把最大最小设置成一样的值。那究竟是为什么呢?一般而言,Java程序如果你不显示设定该值得话,会自动进行初始化设定。
  -Xmx 的默认值为你当前机器最大内存的 1/4
  -Xms 的默认值为你当前机器最大内存的 1/64 
  显然这样配置的意义是希望JVM可以根据当前运行的环境,动态伸缩堆内存大小。之所以生产上设置成固定大小,网上也是说法不一,很多时候都是使用“防止内存抖动”这样的模糊词语给出解释。但是我相信各位读者也很懵,不知道这个词具体表达什么含义。
  所以接下来我打算用这篇文章来着重解释一下这其中的门道。带大家彻底弄懂设置固定大小堆的底层原理和好处。为了能顺利看懂本文,我假设你们已经具备了一定的操作系统基础知识。
最大堆或最小堆,从字面上理解就是JVM在运行Java程序时,为其分配堆内存空间的上限和下限值。我们把最大和最小堆设置成相同值那意思就是分配了固定大小的内存呗。这样不就省去了动态调整内存(申请和释放)以及频繁的用户态和内核态的切换带来的开销吗?。如下图所示。
  看上去就是这么回事,简单明了。然而当我们尝试去做个模拟实验,事实却并非如此。比如,随便写个Java程序,使用如下命令启动之。并设置好固定大小堆为1G。
  java -Xmx1024m -Xms1024m -jar demo.jar
  然后我们通过查看进程的内存占用时,发现程序并没有占用1G的空间,而是很小的占用。这个实验结果和我们预期的完全不一致。究竟是什么原因呢?
  问题其实出在我们对内存模型的理解上有问题。很多人可能都是像上面图中那样理解程序分配内存的。实际上是不对的,且也更复杂。首先我们要理解一个重要概念,那就是“进程的虚拟地址空间”,我们用户程序通过malloc这个系统调用申请内存,实际上就是申请了一个虚拟的内存,并不是真正的物理内存。大家要注意,这个虚拟的内存就是指“进程的虚拟地址空间”,而不是我们通常理解的Windows下的虚拟内存或Linux下的swap(分区交换)。如下图所示。
  用户程序申请的虚拟内存(虚拟地址空间),也就是通过malloc系统调用,本质就是在进程的虚拟地址空间里分配了一块地址范围而已。32位系统理论上最大4G,每个进程都有自己的虚拟地址空间,都能申请到最大4G内存。但是申请了的内存,如果没有实际使用(写入数据),则操作系统不会给这块虚拟空间分配实际的物理内存。其实原因很简单,物理内存一直属于紧缺资源,所以现代操作系统都设计为由内核程序统一管理,用户程序无权直接干涉。不是说你申请多少就真的给你多少,而是你实际使用多少才会给你多少。
  回到上面那个小实验,你发现启动后程序内存占用很小就是这个原因。尽管JVM已经在你启动时向系统申请了1G的固定堆大小空间。但是由于你这个程序只是一个简单的测试,里面并没有实际的代码操作业务。所以你实际上只用到了很小的物理内存空间。但是如果你的程序真有业务逻辑,随着系统的运行,实际占用物理内存就会越来越多,直到达到申请的上限值1G。运行期间,你的程序同时也会释放一些对象(通过GC),并在适当的时机归还一些物理内存给操作系统。所以占用的物理内存大小,也会动态有所调整。这样操作系统就可以给其他程序使用,提高了内存利用效率。这样的设计也没什么不好的。
  如上图所示,操作系统对内存管理是以页为基本单位的,一个页代表了一个固定大小的地址范围。用户程序给某个变量比如byte[]赋值时,此时该变量对应的进程虚拟地址空间所在的页在物理内存上找不到对应的页映射时,就会触发了一个缺页中断异常,操作系统就会重新将虚拟地址的页映射到物理内存中的页,此时才是真正实现了内存分配,会占用实际的物理内存空间。假如Java程序的GC把这个byte[]变量收回了,也就是不需要占用内存空间了,用户进程的堆管理器会适当的归还一些物理内存给操作系统,以便下次可以给其他任何程序使用。需要注意的是用户程序调用的malloc和free两个系统调用,都是针对用户进程的虚拟地址空间而言的,并不是实际操作物理内存。只有操作系统才拥有对实际物理内存的管理权限。操作系统可以使用有效的各种算法,来独立高效的管理物理内存。这里面的细节,我这里不详细说了,有兴趣的可以去看些操作系统的资料深入了解下。
  然而我们实际的Java程序,配置成固定堆大小后,你会发现,内存占用一旦上去了就下不来了。即使当前程序处于比较空闲的状态下。这又是为什么呢?难道Java的GC没有回收内存?
  其实并不是GC没有回收内存,而是我们这里存在理解问题。GC回收内存并不是指物理内存,而是指当前进程的虚拟内存(虚拟地址空间)。一般而言,回收的虚拟内存并不会立即归还给操作系统,从而操作系统也就无法回收它了。至于何时归还物理内存,这取决于一个叫glibc的堆管理器。它根据一定的策略和算法适当的释放真实的物理内存。否则即便Java程序GC了对象,该对象占用的物理内存也不会立即释放出来。由于这里我们是设置了固定大小的堆空间,实际上GC回收的虚拟内存,也不会被释放归还给操作系统。故Java进程内存占用一旦增长,内存占用几乎都不会再下降了,这样也是出于对象再分配的效率考虑的。这样显然可以避免操作系统反复把进程的虚拟地址页复映射物理内存页(缺页中断异常)操作,导致频繁的用户态和内核态切换造成的性能问题。
 

Java生产环境JVM设置成固定堆大小深层原理的更多相关文章

  1. Java生产环境下性能监控与调优详解视频教程 百度云 网盘

    集数合计:9章Java视频教程详情描述:A0193<Java生产环境下性能监控与调优详解视频教程>软件开发只是第一步,上线后的性能监控与调优才是更为重要的一步本课程将为你讲解如何在生产环境 ...

  2. 设置JVM参数,查看堆大小

    1.在eclipse设置JVM参数     打开eclipse-窗口-首选项-Java-已安装的JRE(对在当前开发环境中运行的java程序皆生效,也就是在eclipse中运行的java程序)编辑当前 ...

  3. jvm详情——6、堆大小设置简单说明

    年轻代的设置很关键JVM中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制:系统的可用虚拟内存限制:系统的可用物理内存限制.32位系统下,一般限制在1.5G~2G:64 ...

  4. IT兄弟连 Java语法教程 Java开发环境 JVM、JRE、JDK

    要想开发Java程序,就需要知道什么是JVM.JRE以及JDK.JVM是运行Java程序的核心,JRE是支持Java程序运行的环境,而JDK是Java开发的核心,下面我们分别具体介绍它们以及它们之间的 ...

  5. java生产环境增量发版陷阱【原】

    前言 在生产环境,我们为了降低发版风险,一般都只做增量发布,不做全量发布. 除非项目只有一到两人开发,对时间线和代码脉络结构一清二楚,才可全量发布. 然而增量发布也是有一定隐藏陷阱在里面的,以下就是笔 ...

  6. JAVA虚拟机环境变量设置

    转自: 网络    下载java环境变量设置所需的jdk并安装,下载地址:http://www.oracle.com/technetwork/cn/java/javase/downloads/jdk7 ...

  7. 【Odoo 8开发教程】第二章:Odoo生产环境部署设置

    转载请注明原文地址:https://www.cnblogs.com/cnodoo/p/10792977.html 一:dbfilter 数据库访问规则设置 一个odoo实例可以连接到不同的数据库实例中 ...

  8. java jdk 环境变量设置

    我的电脑点右键,选择“属性”,选择“高级”标签,进入环境变量设置,分别设置如下三个环境变量: 设置JAVA_HOME: 一是为了方便引用,比如,JDK安装在C:\jdk1.6.0目录里,则设置JAVA ...

  9. [Java] Tomcat环境变量设置

    @echo off title Tomcat环境变量设置 color 0a set /p inputTH=D:\Work\024_Tomcat if /i "%inputTH%"= ...

随机推荐

  1. CentOS 7安装docker环境

    一.环境准备 Docker支持以下的CentOS版本: 1.Docker运行在CentOS 7(64-bit)上要求系统为64位,系统内核版本为3.10以上 2.Docker运行在Centos 6.5 ...

  2. Java(31)泛型和可变参数

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228443.html 博客主页:https://www.cnblogs.com/testero ...

  3. DataX的安装及使用

    DataX的安装及使用 目录 DataX的安装及使用 DataX的安装 DataX的使用 stream2stream 编写配置文件stream2stream.json 执行同步任务 执行结果 mysq ...

  4. shell关键字含义

    linux中shell变量$#,$@,$0,$1,$2的含义解释: 变量说明: $$ Shell本身的PID(ProcessID) $! Shell最后运行的后台Process的PID $? 最后运行 ...

  5. Codeforces Round #747 (Div. 2) Editorial

    Codeforces Round #747 (Div. 2) A. Consecutive Sum Riddle 思路分析: 一开始想起了那个公式\(l + (l + 1) + - + (r − 1) ...

  6. python查询对像所有方法

    鉴于学习时好多参数对像都不知道是什么玩意有什么方法,搜了半天一个 """arg为一个对像,下面是打印所有对像方法"""for i, func ...

  7. 第三次Scrum Metting

    日期:2021年4月27日会议主要内容概述:确定后端和前端接口,前端讨论画图页面,解决两处画图问题 一.进度情况# 组员 负责 两日内已完成的工作 后两日计划完成的工作 工作中遇到的困难 徐宇龙 后端 ...

  8. SCons - 简单而强大的项目编译脚本(原文https://www.cnblogs.com/binchen-china/p/5646791.html)

    N年前学的makefile,当时还勉强能写一些简单的工程编译,现在已经基本忘了.makefile确实编写复杂,而且平时也不是经常使用,容易忘记.偶识了scons,一切都变的简单了.最近研究了下scon ...

  9. 单片机零基础学习之从“点灯”入门STM32

    本篇文章通过一个简单的例子来熟悉模块化编程以及利用库函数的方法进行开发使用STM32外设的基本流程. 首先,我们打开本讲的例程,在工程目录我们可以看到驱动分组下有 led.delay 两个.c源文件, ...

  10. Django(72)Django认证系统库--djoser

    djoser是什么?   作用:Django认证系统的REST实现.djoser库提供了一组Django Rest Framework视图,用于处理注册.登录.注销.密码重置和帐户激活等基本操作.它适 ...