JVM 内存结构由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:


1、堆

JAVA_OPTS="

-server 

-Xms5G  最大堆内存

-Xmx5G  初始堆内存

"

所有通过new 创建对象内存都在堆中分配,其大小可以通过-Xms -Xmx 来控制,堆被划分新生代和旧生代,新生代又被进一步划分为Eded和Survivor,最后Survivor 由FromSpace 和ToSpace组成

-Xmn 新生代内存大小

 新生代,新建的对象都是用新生代分配内存,Eden 空间不做的时候,会把存活的对象转移到Survivor 中新生代大小可以由-Xmn 来控制,也可以用-XX:SurvivorRatio(存活比例)来控制Eden 和Survivor(存活)的比例旧生代。用于存放新生代多次回收仍然存活的对象

2、栈

每个线程执行每个方法的时候都会在栈中申请一个栈帧每个栈帧包括局部变量和操作数栈,用于存放此方法调用过程中的临时变量,参数和中间结果。

3)、本地方法栈

-XX:PermSize=500M持久代

存放了要加载的类信息,静态变量、final(决定性)类型的常量。属性和方法信息,JVM 用持久代(

PermanetGeneration) 用来存放方法区,可通过-XX:PermSize=500M 、-XX:MaxPermSize=500M 来指定最小值和最大值

 

JVM 垃圾回收机制

 JVM 分别对新生代和旧生代采用不同的垃圾回收机制

新生代通常存活时间较短,因此基于copying 算法拉进行回收,所谓copying 算法就是扫描出存活对象,并复制到一块新的完全未使用的空间,对于新生代,就是eden 和fromspace 或tospace 之间copy 新生代采用空闲指针的方式来控制GC 触发指针保持最后一个分的对象在新生代 区间的位置,当新的对象要分配内存时,用于检测空间是否足够,不够就触发GC 当连续分配对象时,对象会逐渐从eden 到survivor 到最后的旧生代用javavisualVM 来查看,能明显观察到新生代满了后,会把对象转移到旧生代,然后清空继续装载旧生代也满了之后就会报outmemory 的异常如下图:

在执行机制上,JVM 提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge) 和并行GC(ParNew)

  1. 串行GC

    在整个扫描和复制过程中采用单线程的方式来进行,使用于单CPU 、新生代空间较小及对暂时时间要求不是非常高的应用上,是cline 级别默认的GC 方式,可以通过-XX:UseSerialGC(连续的)来强制指定。

  2. 并行回收GC

    在整个扫描和复制过程采用多线程的方式进行,适用于多cpu 对暂时要求较短的应用上,是server级默认采用的GC 方式可用-XX:UseParallelGC (并行的)来强制指定,用-XX:ParallelGCThreads=4来指定线程数

  3. 并行GC

    与旧生代的并发GC 配合使用

    旧生代的GC 旧生代与新生代不同,对象存活时间较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出来存活的对象,然后在进行回收未标记的对象, 回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之要减少内存碎片带来的效率损耗。

    在执行机制上JVM 提供了串行GC(SerialMSC) 、并行(parallelMSC) 和并发GC(CMS)具体算法细节还有待进一步深入研究。

以上各种GC机制是需要组合使用的,指定方式由下表所示:

GC机制组合使用

GC机制组合使用

[root@order bin]# cat setenv.sh 

#!/bin/sh

export JAVA_HOME=/usr/local/jdk1.7.0_60/

export JRE_HOME=/usr/local/jdk1.7.0_60/

JAVA_OPTS="-server

-Xms5G                                                             ## 堆内存初始值              

-Xmx5G                                                             ##堆内存最大值

-Xmn2G                                                             ##新生代空间

-Dsun.java2d.noddraw=true                              ##如果硬件加速已经被enable,可以通过这个选项来提高Swing GUI速度,默认值为false

-XX:PermSize=500M                                         ## 表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)                            

-XX:MaxPermSize=500M                                  ##-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。                            

-Xss256k                                                           ##设置每个线程的堆栈大小

-XX:MaxTenuringThreshold=0                          ##在新生代中对象存活次数(经过MinorGC次数)后仍然存活,就会晋升到旧生代

-XX:+UseParNewGC                                        ##设置年轻代多线程收集,可与CMS 收集同时使用。在serial 基础上实现的多线程收集器 

-XX:+UseConcMarkSweepGC                          ##并发标记清楚(CMS)收集器:cms 收集器 也被称为短暂停顿并发收集器。它是对年老代进行垃圾收集的。因为是多线程进行垃圾收回,可与减少停顿时间

-XX:+UseCMSCompactAtFullCollection           ## 表示触发FGC 之后进行压缩,因为CMS 默认不压缩空间的

-XX:CMSFullGCsBeforeCompaction=0            ##,在上一次CMS并发GC执行过后,到底还要再执行多少次full GC才会做压缩。默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩。 把CMSFullGCsBeforeCompaction配置为10,就会让上面说的第一个条件变成每隔10次真正的full GC才做一次压缩(而不是每10次CMS并发GC就做一次压缩,目前VM里没有这样的参数)。这会使full GC更少做压缩,也就更容易使CMS的old gen受碎片化问题的困扰。

-XX:+CMSClassUnloadingEnabled                  ##如果你启用了CMSClassUnloadingEnabled ,垃圾回收会清理持久代,移除不再使用的classes。这个参数只有在 UseConcMarkSweepGC  也启用的情况下才有用

-XX:-CMSParallelRemarkEnabled                    ##降低标记停顿  表示并行remark           

-XX:CMSInitiatingOccupancyFraction=90         ##使用cms作为垃圾回收,使用70%后开始CMS收集,为了保证不出现promotion failed(见下面介绍)错误

-XX:SoftRefLRUPolicyMSPerMB=0                  ##在最后一次引用时,软可达对象将保持一定的时间 等待多少秒

-XX:LargePageSizeInBytes=128M                   ##设置用于Java堆的大页面尺寸

-XX:+UseFastAccessorMethods                       ##原始类型的快速优化

-XX:+UseCMSInitiatingOccupancyOnly            ##使用手动定义初始化定义开始CMS收集

-XX:+PrintClassHistogram                                ##在垃圾收集之前执行

-XX:+PrintGCDetails                                         ##GC 输出

 -XX:+PrintGCTimeStamps                               ##GC 输出

 -XX:+PrintHeapAtGC                                       ##打印GC前后的详细堆栈信息

-Xloggc:${CATALINA_HOME}/logs/gc.log"       ##把相关日志信息记录到文件以便分析.与上面几个配合使用

要了解Java垃圾收集机制,先理解JVM内存模式是非常重要的。今天我们将会了解JVM内存的各个部分、如何监控以及垃圾收集调优。

Java(JVM)内存模型

正如你从上面的图片看到的,JVM 内存被分成多个独立的部分,广泛的说,JVM 堆内存被分为两部分---年轻代(young Generation)和老年代(old Generation)

年轻代

年轻代是所有新对象产生的地方,当年轻代内存空间被用完时,这个垃圾回收叫做MinorGC 年轻代,被分为3个部分---Enden 区和两个Survivor区

年轻代空间的要点:

        大多数新建的对象都位于Eden区

        当Eden区 被对象填满时,就会执行Minor GC并吧所有存活下来的对象转移到其中一个survivor 区

        Minor GC 同样会检查存活下来的对象,并把它们转移另一个survivor区,这样在一段时间内,总会有一个空的survivor 区

        经过多次GC周期后,任然存活下来的对象会被转移到年老代内存空间,通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的


年老代

    年老代内存里包含长期存活的对象和经过多次Minor GC 后依然存活下来的对象,通常会在老年代内存被占满时进行垃圾回收。老年代的垃圾收集叫做Major GC 。Major GC 会发给更多的时间

Stop the World 事件

    所有的垃圾收集都是“Stop the World” 事件,因为所有的应用线程都会停下来直到操作完成(所以叫 Stop the World)

    因为年轻代里的对象都是一些临时(short-lived)对象,执行Minor GC 非常快,所以应用不会受到(“Stop the World”)影响。

   由于Major GC 会检查所有存活的对象因此会花费更长的时间,应该尽量减少Major GC 会在垃圾回收期间让你的应用反应迟钝,所以如果你有一个需要快速响应发生多次Major GC,你就会看到超时错误。

 垃圾回收时间取决于垃圾回收策略,这就是为什么有必要去监控垃圾收集和对垃圾收集进行调优,从而避免要求快速响应的应用出现超时错误。

永久代

永久代或者“Perm Gen”包含了JVM需要的应用元数据,这些元数据描述了在应用里使用的类和方法。注意,永久代不是Java堆内存的一部分。

永久代存放JVM运行时使用的类。永久代同样包含了Java SE库的类和方法。永久代的对象在full GC时进行垃圾收集。

方法区

方法区是永久代空间的一部分,并用来存储类型信息(运行时常量和静态变量)和方法代码和构造函数代码。

内存池

如果JVM实现支持,JVM内存管理会为创建内存池,用来为不变对象创建对象池。字符串池就是内存池类型的一个很好的例子。内存池可以属于堆或者永久代,这取决于JVM内存管理的实现。

运行时常量池

运行时常量池是每个类常量池的运行时代表。它包含了类的运行时常量和静态方法。运行时常量池是方法区的一部分。

Java栈内存

Java栈内存用于运行线程。它们包含了方法里的临时数据、堆里其它对象引用的特定数据。你可以阅读栈内存和堆内存的区别

Java 堆内存开关

Java提供了大量的内存开关(参数),我们可以用它来设置内存大小和它们的比例。下面是一些常用的开关:

VM 开关

VM 开关描述

-Xms

设置JVM启动时堆的初始化大小。

-Xmx

设置堆最大值。

-Xmn

设置年轻代的空间大小,剩下的为老年代的空间大小。

-XX:PermGen

设置永久代内存的初始化大小。

-XX:MaxPermGen

设置永久代的最大值。

-XX:SurvivorRatio

提供Eden区和survivor区的空间比例。比如,如果年轻代的大小为10m并且VM开关是-XX:SurvivorRatio=2,那么将会保留5m内存给Eden区和每个Survivor区分配2.5m内存。默认比例是8。

-XX:NewRatio

提供年老代和年轻代的比例大小。默认值是2。

Java垃圾回收

Java垃圾回收会找出没用的对象,把它从内存中移除并释放出内存给以后创建的对象使用。Java程序语言中的一个最大优点是自动垃圾回收,不像其他的程序语言那样需要手动分配和释放内存,比如C语言。

垃圾收集器是一个后台运行程序。它管理着内存中的所有对象并找出没被引用的对象。所有的这些未引用的对象都会被删除,回收它们的空间并分配给其他对象。

一个基本的垃圾回收过程涉及三个步骤:

  1. 标记:这是第一步。在这一步,垃圾收集器会找出哪些对象正在使用和哪些对象不在使用。

  2. 正常清除:垃圾收集器清会除不在使用的对象,回收它们的空间分配给其他对象。

  3. 压缩清除:为了提升性能,压缩清除会在删除没用的对象后,把所有存活的对象移到一起。这样可以提高分配新对象的效率。

简单标记和清除方法存在两个问题:

  1. 效率很低。因为大多数新建对象都会成为“没用对象”。

  2. 经过多次垃圾回收周期的对象很有可能在以后的周期也会存活下来。

上面简单清除方法的问题在于Java垃圾收集的分代回收的,而且在堆内存里有年轻代年老代两个区域。我已经在上面解释了Minor GC和Major GC是怎样扫描对象,以及如何把对象从一个分代空间移到另外一个分代空间。

Java垃圾回收类型

这里有五种可以在应用里使用的垃圾回收类型。仅需要使用JVM开关就可以在我们的应用里启用垃圾回收策略。让我们一起来逐一了解:

  1. Serial GC(-XX:+UseSerialGC):Serial GC使用简单的标记、清除、压缩方法对年轻代和年老代进行垃圾回收,即Minor GC和Major GC。Serial GC在client模式(客户端模式)很有用,比如在简单的独立应用和CPU配置较低的机器。这个模式对占有内存较少的应用很管用。

  2. Parallel GC(-XX:+UseParallelGC):除了会产生N个线程来进行年轻代的垃圾收集外,Parallel GC和Serial GC几乎一样。这里的N是系统CPU的核数。我们可以使用 -XX:ParallelGCThreads=n 这个JVM选项来控制线程数量。并行垃圾收集器也叫throughput收集器。因为它使用了多CPU加快垃圾回收性能。Parallel GC在进行年老代垃圾收集时使用单线程。

  3. Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一样。不同之处,Parallel Old GC在年轻代垃圾收集和年老代垃圾回收时都使用多线程收集。

  4. 并发标记清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被称为短暂停顿并发收集器。它是对年老代进行垃圾收集的。CMS收集器通过多线程并发进行垃圾回收,尽量减少垃圾收集造成的停顿。CMS收集器对年轻代进行垃圾回收使用的算法和Parallel收集器一样。这个垃圾收集器适用于不能忍受长时间停顿要求快速响应的应用。可使用 -XX:ParallelCMSThreads=n JVM选项来限制CMS收集器的线程数量。

  5. G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7后才可以使用的特性,它的长远目标时代替CMS收集器。G1收集器是一个并行的、并发的和增量式压缩短暂停顿的垃圾收集器。G1收集器和其他的收集器运行方式不一样,不区分年轻代和年老代空间。它把堆空间划分为多个大小相等的区域。当进行垃圾收集时,它会优先收集存活对象较少的区域,因此叫“Garbage First”。你可以在Oracle Garbage-FIrst收集器文档找到更多详细信息。