{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/2020-06-20-jmm/","result":{"data":{"markdownRemark":{"id":"072f5260-63c5-5aa1-a4b3-01a7f60f9edd","html":"<p><img src=\"/img/flavor_wheel.jpg\" alt=\"flavor wheel\"></p>\n<p>对于 Java 开发人员来说，了解 Java 内存模型是必不可少的，他们需要开发，部署，监视，测试和调整 Java 应用程序的性能。</p>\n<h1>Java Memory Model</h1>\n<p>运行资源密集型Java程序时，您必须使用以下某些JVM内存配置。</p>\n<ul>\n<li>-XmsSetting - 初始化堆大小</li>\n<li>-XmxSetting - 最大堆大小</li>\n<li>-XX:NewSizeSetting - 新一代堆大小</li>\n<li>-XX:MaxNewSizeSetting - 最大新生代堆大小</li>\n<li>-XX:MaxPermGenSetting - 永久代的最大大小</li>\n<li>-XX:SurvivorRatioSetting - 新的堆大小比率（例如，如果Young Gen大小为10m，并且内存开关为–XX：SurvivorRatio = 2，则将为Eden空间保留5m，为两个Survivor空间分别保留2.5m，默认值为8）</li>\n<li>-XX:NewRatio - 提供新旧大小的比率（默认值= 2）</li>\n</ul>\n<p>那么 JVM 如何驻留在内存中？就像任何其他软件一样，JVM占用主机OS内存上的可用空间。</p>\n<p><img src=\"/img/java/os-jvm-memory.png\" alt=\"os memory\"></p>\n<p>但是，在 JVM 内部，存在单独的内存空间（堆，非堆，缓存），以便存储运行时数据和编译后的代码。</p>\n<p><img src=\"/img/java/jvm-memory.png\" alt=\"JVM memory\"></p>\n<h2>1 堆内存</h2>\n<ul>\n<li>堆分为两部分 - 年轻一代和老年一代</li>\n<li>JVM 启动时分配堆（初始大小：-Xms）</li>\n<li>应用程序运行时堆大小增加/减少</li>\n<li>最大大小：-Xmx</li>\n</ul>\n<p><img src=\"/img/java/jvm-heap.png\" alt=\"JVM Heap\"></p>\n<h3>1.1 新生代（Young Gen）</h3>\n<ul>\n<li>保留用于包含新分配的对象</li>\n<li>Young Gen 包括三个部分 - Eden Memory 和两个 Survivor Memory 空间（S0，S1）</li>\n<li>大多数新创建的对象进入 Eden 空间。</li>\n<li>当 Eden 空间中装满对象时，将执行次要 GC（又称 Young Collection），并将所有幸存者对象移至其中一个幸存者空间。</li>\n<li>次要 GC 还可以检查幸存者对象并将其移至其他幸存者空间。因此，一个幸存者空间始终是空的。</li>\n<li>在许多次 GC 循环后仍然存在的对象将移至老年代存储空间。通常，可以通过设置年轻一代对象的年龄阈值，然后才有资格晋升为老年代对象。</li>\n</ul>\n<h3>1.2 老年代（Old Generation）</h3>\n<ul>\n<li>这是为包含可能在多轮次要 GC 中存活下来的长寿命对象而保留的</li>\n<li>当老年代空间已满时，将执行主要 GC（又称 Old Collection）（通常需要更长的时间）</li>\n</ul>\n<h2>2 非堆内存（Non-Heap Memory）</h2>\n<ul>\n<li>包括永久代（ Permanent Generation 从 Java 8 开始由元空间 - Metaspace 替换）</li>\n<li>Perm Gen 存储每个类的结构，例如运行时常量池，字段和方法数据，以及方法和构造函数的代码以及内部字符串</li>\n<li>可以使用 -XX：PermSize 和 -XX：MaxPermSize 更改其大小</li>\n</ul>\n<p><img src=\"/img/java/non-heap-memory.png\" alt=\"Non-Heap Memory\"></p>\n<h2>3 缓存内存</h2>\n<ul>\n<li>\b包括代码缓存.</li>\n<li>存储由 JIT 编译器生成的已编译代码（即原生代码 Machine Code），JVM 内部结构，已加载的探查器代理代码和数据等。</li>\n<li>当代码缓存超过阈值时，它将被刷新（并且 GC 不会重新定位对象）。</li>\n</ul>\n<h2>栈</h2>\n<p>这是一个很好的示例（来自 baeldung.com），说明了 Stack 和 Heap 如何有助于执行一个简单的程序（使用代码检查堆栈顺序）</p>\n<pre><code class=\"language-java\">class Person {\n    int pid;\n    String name;\n// constructor, setters/getters\n}\n\npublic class Driver {\n    public static void main(String[] args) {\n        int id = 23;\n        String pName = \"Jon\";\n        Person p = null;\n        p = new Person(id, pName);\n    }\n}\n</code></pre>\n<p><img src=\"/img/java/stack-heap-demo.png\" alt=\"Stack &#x26; Heap\"></p>\n<p>注：仔细阅读供应商文档，以了解适用于您的 JVM 版本的工具。</p>\n<h2>常见内存相关问题</h2>\n<ul>\n<li>java.lang.StackOverFlowError - 栈内存已满</li>\n<li>java.lang.OutOfMemoryError: Java heap space - 堆内存已满</li>\n<li>java.lang.OutOfMemoryError: GC Overhead limit exceeded - GC已达到其开销限制</li>\n<li>java.lang.OutOfMemoryError: Permgen space - 永久代空间已满</li>\n<li>java.lang.OutOfMemoryError: Metaspace - 元空间已满（从 JDK8 开始）</li>\n<li>java.lang.OutOfMemoryError: Unable to create new native thread - JVM 本机代码无法再从底层操作系统创建新的本机线程，因为已经创建了这么多线程，并且它们消耗了 JVM 的所有可用内存</li>\n<li>java.lang.OutOfMemoryError: request size bytes for reason - 交换内存空间已被应用程序完全消耗</li>\n<li>java.lang.OutOfMemoryError: Requested array size exceeds VM limit - 我们的应用程序使用的数组大小大于基础平台允许的大小</li>\n</ul>\n<p>但是，这些输出只能指示 JVM 的影响，而不能指示实际的错误。实际错误及其根本原因可能会出现在代码中的某处（例如内存泄漏，GC 问题，同步问题），资源分配甚至硬件设置。不建议您简单地增加受影响的资源来解决问题。需要监视资源使用情况，分析每个类别，进行堆转储，检查和调试、优化代码等。如果行不通再加资源。</p>\n<h2>References</h2>\n<p><a href=\"https://medium.com/platform-engineer/understanding-java-memory-model-1d0863f6d973\">Understanding JMM</a></p>","frontmatter":{"date":"June 20, 2020","title":"Java 内存模型","description":"Java Memory Model","tags":null}}},"pageContext":{"id":"072f5260-63c5-5aa1-a4b3-01a7f60f9edd"}}}