Java生産環境JVM設置成固定堆大小深層原理-編程思維

  可能很多人都知道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高性能編程第一章-Java多線程概述-編程思維

目标 重點: 線程安全的概念 線程通信的方式與應用 reactor線程模型 線程數量的優化 jdk常用命令 Netty框架的作用 難點 java運行的原理 同步關鍵字的原理 AQS的抽象 JUC的源碼 網絡編程的概念 GC機制 class文件内容 文件開頭有一個0xcafebabe特殊的标志。 包含版本、訪問标志

對象池模式(Object Pool Pattern)-編程思維

本文節選自《設計模式就該這樣學》 1 對象池模式的定義 對象池模式(Object Pool Pattern),是創建型設計模式的一種,将對象預先創建并初始化後放入對象池中,對象提供者就能利用已有的對象來處理請求,減少頻繁創建對象所占用的内存空間和初始化時間。 一個對象池包含一組已經初始化并且可以使用的對象,可以在有需

空對象模式(Null Object Pattern)-編程思維

本文節選自《設計模式就該這樣學》 1 空對象模式的定義 空對象模式(Null Object Pattern)不屬于GoF設計模式,但是它作為一種經常出現的模式足以被視為設計模式了。其具體定義為設計一個空對象取代NULL對象實例的檢查。NULL對象不是檢查控制,而是反映一個不做任何動作的關系。這樣的NULL對象也可以在

Spring @Autowired 注解自動注入流程是怎麼樣?-編程思維

面試中碰到面試官問:”Spring 注解是如果工作的?“,當前我一驚,完了這不觸及到我的知識誤區了嗎?,還好我機智,靈機一動回了句:Spring 注解的工作流程倒還沒有看到,但是我知道@Autowired注解的工作流程,後面不用說了一頓巴拉,面試官都連連點頭。 面試中要活用轉移話題,要避免回答 ”不知道“,要引導面試官

拜托,不要再問我線程池啦!-編程思維

Java提供了幾種便捷的方法創建線程池,通過這些内置的api就能夠很輕松的創建線程池。在java.util.concurrent包中的Executors類,其中的靜态方法就是用來創建線程池的: newFixedThreadPool():創建一個固定線程數量的線程池,而且線程池中的任務全部執行完成後,空閑的線程也不會被關

Linux安裝配置Maven詳解-編程思維

Maven介紹 Maven是一個Java項目管理和構建工具,它可以定義項目結構、項目依賴,并使用統一的方式進行自動化構建,是Java項目不可缺少的工具。 Maven有一套标準化的構建流程,可以自動化實現編譯,打包,發布,等等。 簡單來說,java項目需要的依賴包統一交給maven去管理,然後通過maven工具将項目打成

Linux安裝配置JDK環境詳解-編程思維

1、下載工具 下載地址:https://www.oracle.com/cn/java/technologies/javase/javase-jdk8-downloads.html (安裝過程使用jdk-8u231-linux-x64.tar.gz) 2、安裝配置jdk 2.1 将壓縮包上傳到/opt下 2.2 将壓縮