1 线程共享内存区
Java堆区
- 用于存储Java对象实例,可是不必定是Java对象内存分配的惟一选择(为了下降GC频率).在JVM启动的时候大小就已经设定好了.(-Xmx最大 -Xms起始) 超过最大内存的时候,抛出OOM异常.
- 实际的内存空间能够不连续,是GC的重点区域.
- YoungGen新生代(Eden, From Survivor, To Survivor) ; OldGen老年代(OldGen).根据不一样的代选择不一样的GC算法.
方法区
- 存储了每个Java类的结构信息,好比 运行时常量池 , 字段和方法数据 , 构造函数 , 普通方法的字节码内容 以及 类,实例,接口初始化时要用到的特殊方法等数据.
- jvm规范没有对方法区的实现有明确要求,在HotSpot中,它属于Java堆区的一部分
- 在JVM启动的时候建立,也能够不连续,有时候被称为永久代,它不会频繁的GC,可是不表明它里面数据永远不会被回收.若是没有显示要求不对方法区进行内存回收的状况下,GC的回收目标仅针对方法区的常量池和类型卸载
- 超过
-XX:Max-PermSize
也会OOM
运行时常量池
- 属于方法区的一部分.运行时常量池就是字节码文件中常量池表的运行时表示形式.
- 当类加载器成功的将一个类或者接口装载进JVM后,就会建立与之对应的运行时常量池.
2 线程私有内存区
PC寄存器
- 无OOM
- 为何每一个线程都要?答:CPU作任务切换,每一个线程都要记录正在执行的当前字节码指令地址,从而每一个线程均可以独立计算.
- JVM的解释器须要经过改变PC寄存器的值来明确下一条有关执行什么样的字节码指令
Java栈(Java虚拟机栈)
- Java栈用于存储栈帧,生命周期和线程的生命周期一致
- 对应Java堆中存储对象,Java栈中局部变量表用于存储各种原始数据类型,对象引用,以及returnAdress类型.
3 自动内存管理
内存分配原理
-
jvm包含三种引用了类型:类类型,数组类型,接口类型,分别对应建立的值是 类实例, 数组实例, 以及实现了某个接口的派生类实例.java
-
下图是jvm中具体的建立对象实例
算法
-
分配内存有指针碰撞(已用的未用的分两边,经过修改中间指针的偏移量) 和 空闲列表(Free List)数组
堆区和方法区
- 堆区和方法区是线程共享,在并发环境下从堆区中划份内存空间是非线程安全的.因此一个类若是在分配内存以前已经成功完成类装载步骤以后,JVM会优先选择在TLAB(本地线程分配缓冲区,是堆区中一块线程私有的区域,包含在Eden空间内,缺省很小,占Eden的1%,能够经过参数调整),能够避免一系列的非线程安全问题,还能提高内存分配的吞吐量.这种内存分配方式叫作快速分配策略.(-XX:UseTLAB)
- 一旦对象在TLAB空间分配内存失败,JVM会尝试经过加锁来确保原子性,从而直接在Eden分配内存.
- 分配好内存空间后,接下来就是初始化对象实例,首先对分配的内存空间进行零值初始化,再接下来是初始化对象头和实例数据.到这里,一个Java对象实例才是真正的建立成功.
逃逸分析与栈上分配
- 一个对象被定义在方法体内部以后,一旦其应用被外部成员引用,这个对象就发生了逃逸,反之,JVM就会为其在栈帧中分配内存空间(这是优化技术)
public class StackAllocation {
public StackAllocation obj;
//逃逸
public StackAllocation getStackAllocation() {
return null == obj ? new StackAllocation() : obj;
}
//为成员变量赋值,逃逸
public void setStackAllocation() {
obj = new StackAllocation();
}
//引用成员变量的值,逃逸
public void useStackAllocation1() {
StackAllocation obj = getStackAllocation();
}
//对象的做用于仅在方法体内,未逃逸
public void useStackAllocation2() {
StackAllocation obj = new StackAllocation;
}
}
对象内存布局与OOP-Klass模型
GC算法和垃圾收集器
- GC(Garbage Collector 垃圾收集器),其实内存划分(新生代,老年代)是彻底依赖于GC的设计.当内存空间中的内存消耗达到必定的阈值以后,GC会进行垃圾回收.
- 能够根据如下6点评估一款GC的性能
- 吞吐量:程序的运行时间/(程序的运行时间 + 内存回收的时间)
- 垃圾收集开销
- 暂停时间
- 收集频率
- 堆空间:java堆区锁占用的内存大小
- 快速:一个对象从诞生到被垃圾回收所经历的时间.
垃圾标记: 根搜索算法
- 通常不用引用计数法.没法解决死亡对象的互相引用致使没法垃圾回收,因此HotSpot使用根搜索算法,只有可以被根对象集合直接或者间接链接的对象才是存活对象.当目标对象不可达的时候,即可以在instanceOopDesc的MarkWord中将其标记为垃圾对象.
- 根对象集合:
- Java栈中的对象引用
- 本地方法栈中的对象引用
- 运行时常量池中的对象引用
- 方法区中类静态属性的对象引用(因此这个要注意 静态变量的定义!)
- 与一个类对应的惟一数据类型的Class对象
垃圾回收:分代收集算法
- 上一步成功的区分出了存活和死亡对象.接下来就是垃圾回收算法.
标记-清除算法(Mark-Sweep)
- 把垃圾回收任务分为两个阶段,分别是垃圾回收和内存释放.简单,可是效率低下,并且会产生内存碎片
复制算法(Copying)
-
就是由于这个算法,因此内存是分代的!安全
-
java的大多数对象都是瞬时对象,生命周期很是短.复制算法普遍用于新生代中.新生代分为Eden,From Suvivor和To Suvivor,Eden和另外两个Survior空间缺省所占比例为8:1,能够经过-XX:SurvivorRatio
调整.并发
-
当执行一次Minor GC,Eden中存活的对象会被复制到To Suvivor空间内,而且以前经历过一次Minor GC并在From Suvivor存活下来的对象若是还年轻的话会被复制到To Suvivor.jvm
-
知足两种特殊状况,Eden和From空间的存活对象不会被复制到To空间.函数
- 存活的对象的分代年龄超过
-XX:MaxTenuringThreshold
所指的的阈值时,会直接晋升到老年代中.
- 当To空间的容量达到阈值的时候,存活的对象也会直接晋升到老年代中
- 当执行完Minor GC以后,Eden空间和From空间将会被清空,存活下来的对象会所有存在To空间内,接下来From空间和To空间将会互换位置(无非就是使用To空间做为一个临时的空间交换角色,因此务必保证一个survivor空间是空的)
- 不适用与老年代中的内存回收,由于老年代中的对象的生命周期都比较长,因此会有不少的复制,效率和效果都不太好
标记-压缩算法(Mark-Compact)
- 适用于老年代
- 当标记出垃圾对象以后,会将全部的存活对象移动到一个规整且连续的空间,而后执行Full GC(Major GC),垃圾回收以后,已用和未用的内存都各自一边.
总结
- 新生代的GC算法一速度优先,新生代的对象生命周期都很是短暂,内存空间也比较小.因此这块的垃圾回收会很是频繁.
- 老年代的GC算法使用更节省内存的算法,老年代的对象生命周期都比较长,而且老年代占了绝大部分的堆空间.
垃圾收集器
-
上面说的是JVM的内存回收算法,接下来讲的是GC的具体实现.有许多的GC版本,好比:Serial/Serial Old,Parallel/Parallel Old,CMS,G1布局
-
这些新生代和老年代的GC算法能够自由组合
性能
-
两个很是重要的概念:串行仍是并行回收,并发仍是"Stop-the-World"机制优化
-
串行回收简单来讲旧书多个CPU能够用时,也只有一个CPU用于垃圾回收操做,而且在执行垃圾回收的时候,程序中的工做线程会被暂停(注意这个的区别),串行回收缺省在Client模式下的JVM.并行收集能够运用多个CPU同时进行垃圾收集,提高了吞吐量,可是仍是要"STW",这个必定要注意.
-
第二个概念是说的是回收的时候,要不要stw的,最新的G1也作不到彻底不须要STW.
串行回收:Serial收集器
- 用于新生代,采用复制算法,串行回收和STW.是Client下的缺省新生代GC
- Serial Old 采用标记-压缩算法,其余同样.
-XX:+UseSerialGC
并行回收:ParNew收集器
- 用并行方式执行内存回收,其余和Seial几乎没有区别.在单个cpu下,ParNew不见得会比Serial高效
- 在某些注重低延迟的应用场景中,ParNew+CMS(Concurrent-Mark-Sweep)收集器组合执行Server模式下的内存回收几乎是最佳的选择.
-XX:+UseParNewGC
程序吞吐量优先:Parallel收集器
- 复制算法,并行回收和STW,
- 和ParNew最大的不通是Parallel能够控制程序的吞吐量大小.
-XX:GCTimeRatio
设置内存回收的时间所占JVM运行总时间的比例(也就是控制GC的执行频率,公式为1/(1+N),默认值是99,也就是说将只有1%的时间用于执行垃圾回收)
-XX:MaxGCPauseMillis
设置STW的暂停时间阈值,若是指定了该选项,Parallel收集器将会尽量地在指定时间范围完成内存回收.
- 注意!吞吐量和低延迟这两个目标实际上是存在相互竞争的矛盾,若是选择吞吐量优先,就会下降内存回收的执行频率,这会致使GC须要更长的暂停时间来执行垃圾回收.若是选择以低延迟优先,那么为了下降每次GC的时间,只能更加频繁的进行GC,这会致使吞吐量降低.
-XX:UseAdaptiveSizePolicy
用于甚至GC的自动分代大小调整策略,这个JVM就是自动调整相关参数
- Parallel Old采用标记-压缩算法,Parallel+parallel Old是个不错的选择
低延迟:CMS(Concurrent-Mark-Sweep)
- 是优秀的老年代收集器,采用标记-清除算法,也会stw
- 有如下阶段:
- 初始标记阶段
- 并发标记阶段
- 再次标记阶段,由于工做线程和垃圾回收线程共同工做,因此须要再次标记.因此不可避免的仍是会有漏网之鱼
- 并发清除阶段
- 因为采用标记-清除(通常老年代采用标记压缩),因此不可避免的会有内存碎片,因此使用CMS为新对象分配内存空间的时候,将没法使用指针碰撞技术,只能选择空闲列表进行内存分配
-XX:+UseCMSCompactAtFullCollection
,用于在执行完指定的FullGC以后是否对内存空间进行压缩整理.
-XX:CMSFullGCsBeforeCompaction
用于设置执行多少次Full GC以后对内存空间进行压缩整理
- 一个要纠正的点是 Full-GC是遍及整个堆空间的,不仅是在老年代中.而CMS能够提供
-XX:CMSInitiatingOccupancyFraction
用于设置当老年代的内存使用率达到多少的时候执行内存回收.(缺省92%) 注意 这里的内存回收仅限于老年代,因此能够有效的下降FullGC的次数
区域分代式:G1(Garbage-First)收集器
Java虚拟机精讲