JAVA内存管理
JAVA内存管理
JVM结构

- Class Loader
类加载器的作用是根据给定的全限定名类名(如
java.lang.Object)来装载class 文件的内容到Runtime data area 中的method
area(方法区域)。如HelloWord.java程序然后通过javac编译成class文件,ClassLoader负责加载class文件到内存。 - Execution Engine
执行引擎也叫做解释器(Interpreter),执行classes中的指令。任何JVM
specification 实现(JDK)的核心是Execution engine, 换句话说:Sun 的JDK
和IBM 的JDK 好坏主要取决于他们各自实现的Execution engine 的好坏。每个
运行中的线程都有一个Execution engine的实例。 - Native Interface
本地接口的作用是:与native libraries 交互,是其它编程语言交
互的接口,如融合C/C++程序。 -
Runtime data area
运行数据区,将程序都被加载到这里,进行运行。JVM的内存管理也就是围绕着Runtime data area区域。下面重点介绍此区域。
JVM内存模型(Runtime data area)
Runtime data area 主要包括五个部分:Heap (堆), Method Area(方法区域),
Java Stack(java 的栈), Program Counter(程序计数器), Native method
stack(本地方法栈)。Heap 和Method Area 是被所有线程的共享使用的;而Java
stack, Program counter 和Native method stack 是以线程为粒度的,每个线
程独自拥。(这里可能出现的异常java.lang.OutOfMemoryError:
Java heap space)
- Heap:每一个java程序有一个jvm实例,一个jvm实例只有一个堆空闲,所有线程共享。这就需要考虑多线程访问堆数据的同步问题。
- Method area:被转载的class信息存储在Method area内存中。当虚
拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个
class文件内容并把它传输到虚拟机中。紧接着虚拟机提取其中的类型信息,并
将这些信息存储到方法区。该类型中的类(静态)变量同样也存储在方法区中。(这里可能出现的异常java.lang.OutOfMemoryError: PermGen full) - Java Stack:。每当线程调用一个方法的时候,
就对当前状态作为一个帧保存到java stack 中(压栈);当一个方法调用返回时,
从java stack 弹出一个帧(出栈)
public class TestStackOverFlow {
//Exception in thread "main" java.lang.StackOverflowError
public static void main(String[] args) {
System.out.println("start------------");
Recursion recursion = new Recursion();
recursion.doAction(100000);
System.out.println("end-----------");
}
static class Recursion {
public int doAction(int n) {
if (n <= 1) {
return 1;
}
return n + doAction(n - 1);
}
}
}
- Program counter:每个运行中的Java 程序,每一个线程都有它自己的PC寄存器。PC 寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
- Native method stack:对于一个运行中的Java 程序而言,它还能会用到一些跟本地方法相关的数据区。
当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制
的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,不止与
此,它还可以做任何它想做的事情。比如,可以调用寄存器,或在操作系统中分
配内存等。总之,本地方法具有和JVM 相同的能力和权限。(这里出现JVM 无法
控制的内存溢出问题native heap OutOfMemory )
Sun JVM的内存管理
JVM Specification 只是抽象的说明了JVM 实例按照子系统、内存区、数据类型
以及指令这几个术语来描述的,但是规范并非是要强制规定Java 虚拟机实现内
部的体系结构,更多的是为了严格地定义这些实现的外部特征。
Sun JVM 实现中:Runtime data area(JVM 内存) 五个部分中的Java Stack ,
Program Counter, Native method stack 三部分和规范中的描述基本一致;但
对Heap 和Method Area 进行了自己独特的实现,这与Sun JVM的内存管理和Garbage
collector(垃圾回收)机制有关,下面我们来重点介绍。
JVM的分代

备注:
- 新生代中大部分为临时对象,因此采用了复制算法实现,划分为了eden、s0、s1三个区域,或者又称为eden、from、to。
- 通常将对新生代迚行的回收称为Minor GC;对旧生代进行的回收称为Major GC,但由于Major GC除并发GC外均需对整个堆以及持久代进行扫描和回收,因此又称为Full GC。
疑问:Permanent Space属于堆还是Method Area?
Method Area 在HotSpot JVM的实现中属于非堆区,非堆区包括两部分:Permanet Generation和Code Cache,而Method Area属于Permanert Generation的一部分。Permanent Generation用来存储类信息,比如说:class definitions,structures,methods, field, method (data and code) 和 constants。Code Cache用来存储Compiled Code,即编译好的本地代码,在HotSpot JVM中通过JIT(Just In Time) Compiler生成,JIT是即时编译器,他是为了提高指令的执行效率,把字节码文件编译成本地机器代码。对于不同厂家和不同版本的JDK实现有差别,最靠谱的方法还是JVM Specification
更详细的介绍可以参考:http://www.cnblogs.com/duanxz/p/3724120.html
JVM Heap:从广义的角度来说,JVM堆内存在物理上被划分为两个部分Young Generation(年轻代)和Old Generation(老年代)。
- Young Generation(年轻代)
- 年轻代是存放所有被创建的新对象的区域。当年轻代满了的时候,垃圾回收将被执行。这时的垃圾回收叫做 Minor GC. 年轻代又被划分为三个部分-Eden Memory和两个Survivor Memory区域。
- 大多数的新创建对象位于年轻代的Eden Memory区域
- 当Eden区域被对象占满时,Minor GC启动,所有的幸存对象被移动到两个Survivor区域中的一个
- Minor GC也会检查Survivor区域中的对象,并且也会把它们从一个幸存区域移动到另一个幸存区域中。所以,两个幸存区域(S0,S1)总有一个是空的。
- 经过多次GC而幸存下来的对象会移动到Old Generation中,这个次数其实是一个对象年龄(Age)计数器,默认为15,通常会设定一个阈值来指定,可以通过参数 -XX:MaxTenuringThreshold 来设置
- Old Generation(老年代)
- 老年代中的对象通常生命周期较长,经过了多轮的Minor GC后依然存活,则会被复制到老年代。
- Permanent Generation(永久代)
- 方法区中的数据存储于永久代中,其垃圾回收主要分两种:常量和无用的类信息,对于常量的回收,只要是没有引用就可以被回收。
- 对于类信息的回收要满足:类的所有实例被回收;类的ClassLoader被回收;没有通过反射引用该类。
JVM参数说明
(如果你发现上述参数不满足你需求,请读这里,JVM Options Official Page.)
查看命令:jmap–heap [pid]
-Xms:JVM启动时设置初始堆大小,默认是物理内存的1/64;
-Xmx:最大堆大小,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制。一般设置xmx和xms一样,避免每次GC调整堆大小 -Xmn:年轻代大小,剩下的空间分配给老年代
-XX:PermGen:初始永久代大小
-XX:MaxPermGen:最大永久代大小
-XX:SurvivorRatio:Eden区和Survivor区的比率,例如,如果年轻代总共大小为10M,这个参数设置为-XX:SurvivorRatio=2 那么Eden区域获得5M,SO,S1分别是2.5M,默认值为8
-XX:NewRatio:老年代和年轻代的比率,默认值为2
-XX:MaxTenuringThreshold: 用于控制对象在新生代存活的最大次数。默认值15
-XX:PretenureSizeThreshold=x,控制超过多大字节的对象就在old上分配
Garbage Collector
不同厂商的不同版本GC实现会略微不同。下面我们介绍Sun JDK 1.6 HotSpot的版本实现。下面demo代码中会涉及到GC日志,其解读可以参考Understanding Garbage Collection Logs
Serial Copying(串行GC)
单线程收集器,使用单线程完成所有GC工作,没有线程通信,较为高效。使用单处理器机器。JVM client模式下默认GC方式。可通过参数-XX:+UseSerialGC强行指定。
GC触发条件
Minor GC
- eden空间不足
Full GC:
- old空间不足
- perm空间不足
- 显示调用System.gc()
GC触发时的工作
Minor GC:
- 清空eden+from中所有no ref的对象占用的内存
- 将eden+from中所有存活的对象copy到to中
- 在这个过程中一些对象将晋升到old中
- to放不下的
- 存活次数超过tenuringthreshold的
- 重新计算Tenuring Threshold
- 单线程做以上所有动作
- 全过程暂停应用
Full GC:
- 如配置了CollectGen0First,则先触发YGC;
- 清空heap中no ref的对象,permgen中已经被卸载的classloader中加载的class的信息;
- 单线程做以上所有动作;
- 全过程暂停应用。
ParNew(并行GC)
Parallel (并行回收GC)
Server模式下默认;YGC: PSYoungGen(Parallel Scavenge) FGC: Parallel MSC。可以通过-XX:+UseParallelGC或-XX:+UseParallelOldGC来强制指定;
– ParallelGC代表FGC为Parallel MSC
– ParallelOldGC代表FGC为Parallel Compacting
GC触发条件
Minor GC
- eden空间不足
Full GC:
- old空间不足
- perm空间不足
- 显示调用System.gc()
GC触发时的工作
Minor GC:
- 和Serial所作的动作基本相同,不同的为多线程在做这些动作;
- 另一不同:MinorGC的时候的最后不仅重新计算Tenuring Threshold,还会重新调整Eden和From的大小。
Full GC:
- 如配置了ScavengeBeforeFullGC(默认),则先触发YGC;
- MSC: 清空heap中no ref的对象,permgen中已经被卸载的classloader中加载的class的信息,并进行压缩;
- Compacting: 清空heap中部分no ref的对象,permgen中已经被卸载的classloader中加载的class的信息,并进行部分
压缩; - 多线程做以上动作。
Concurrent Mark-Sweep (CMS) Collector
- 可用-XX:+UseConcMarkSweepGC来强制指定;
- 优点:在对Old 进行回收时,对应用造成的暂停时间非常短,适合对latency要求高的应用;
- 缺点:1.内存碎片和浮动垃圾;2.Old区上的内存分配效率低;3.回收的整个耗时比较长;4.和应用争抢CPU;
GC触发条件
Minor GC
- eden空间不足
CMS GC
- Old Gen的使用到达一定的比率,默认为92%;
- 配置了CMSClassUnloadingEnabled,且Perm Gen的使用到达一定的比率,默认为92%;
- Hotspot自己根据估计决定是否要触发;
- 在配置了ExplicitGCInvokesConcurrent 的情况下显示调用了System.gc。
Full GC:
- Promotion Failed 或 Concurrent Mode Failure时;
GC触发时的工作
Minor GC:
- 和Serial所作的动作基本相同,不同的为多线程在做这些动作;
CMS GC
- old gen到达比率时只清除old gen中no ref的对象所占用的空间;
- perm gen到达比率时只清除已被清除的classloader加载的class信息;
Full GC: 和Serial动作完全相同。
更多关于GC和GC的算法可以阅读神级文档jvm hotspot内存管理白皮书
参考文献
命令工具
- jstat –gcutil:在运行时查看堆中各个内存区间的变化以及GC的状况
- jmap –heap:可用查看各个内存区间的大小。
- java -version: 查看jvm在server还是client方式运行
- 启动时可添加-XX:+PrintGCDetails–Xloggc:<file> 输出到日志文件来查看GC的状冴,方便想看就看