JMM--java内存模型

java虚拟机管理的内存分为五大区域:
方法区、堆 ;虚拟机栈、本地方法栈、程序计数器
程序计数器:线程私有、记录当前线程的行号指示器,内存模型中惟一没有OOM错误的区域
虚拟机栈:方法执行时建立,存储局部变量表、操做数栈、动态连接、方法返回地址;先进后出;局部变量表的大小在编译时期就确认了;当请求的栈深度大于当前的栈深度时,报StackOverflowError;栈空间能够动态扩展,当没法申请到足够的空间时,报OOM错误。
本地方法栈:与虚拟机栈相似,虚拟机栈执行java方法,本地方法栈执行native方法,底层使用c或者c++编写。
堆:java虚拟机内存中最大的一块内存区域,存放发对象为线程共享。全部的对象实例及数组都要在堆上分配内存,可是随着JIT编译器的发展和逃逸分析技术的成熟,这个说法也不是那么绝对,可是大多数状况都是这样的。可能会报OOM错误。
    JIT编译器:能够把java字节码转换为能够直接发送给处理器的指令。
    逃逸分析:经过逃逸分析来决定哪些实例或者变量要在堆中进行分配,哪些能够直接在栈上进行分配。这些变量的指针能够被全局引用也能够被其余线程引用。
方法区:线程共享;存储类信息、常量、静态变量、运行时常量池(1.7移除);1.7以前可经过-XX:PermSize和-XX:MaxPermSize限制方法区大小。1.8真正开始废弃永久代,而使用元空间(Metaspace)
 
对象在堆中的布局分为:对象头、实例数据、对齐填充
    对象头(Markword):存储hash码、分代年龄、锁标志位(无锁状态、偏向锁、轻量级锁、重量级锁);
 
栈中局部变量表中的对象引用存储的是 堆中句柄池中的句柄地址,在对象被移动后只须要改变句柄中的实例数据的指针,无需改变ref自己。
若是直接存储对象的实例数据,优势就是速度快,少了一次指针定位的开销时间
 
GC:java堆、方法区须要垃圾回收
堆回收区域:新生代(Eden,from survivor,to survivor)、老年代
判断对象是否存活算法:
1.引用计数算法   优势:效率高,实现简单 缺点:循环引用的问题
2.可达性分析算法
经过“GC Roots”的对象做为起始点,搜索通过的路径称为引用链,当一个对象到GC roots没有任何引用跟它链接则证实对象是不可用的。
能够做为GC ROOTS的对象有四种:
1.栈帧中的本地变量表中的引用对象,就是平时所说的java对象,存放在堆中
2.方法区中的类静态属性引用的对象,也就是static修饰引用的对象,加载类时就加载到内存中
3.方法区中的常量引用的对象
4.本地方法栈中JNI(native方法)引用的对象
 
要真正宣告对象死亡需通过两个过程:
1.可达性分析后没有发现引用链
2.查看对象是否有 finalize方法,若是有重写且在方法内完成自救[好比再创建引用],仍是能够抢救一下,注意这边一个类的finalize只执行一次,这就会出现同样的代码第一次自救成功第二次失败的状况。[若是类重写finalize且还没调用过,会将这个对象放到一个叫作F-Queue的序列里,这边finalize不承诺必定会执行,这么作是由于若是里面死循环的话可能会时F-Queue队列处于等待,严重会致使内存崩溃,这是咱们不但愿看到的。]
 
 
垃圾收集算法:
三大垃圾收集算法:
    1.标记/清除算法
    2.复制算法
    3.标记/整理算法
jvm采用‘分代收集算法’对不一样区域采用不一样的回收算法
新生代采用复制算法 :Eden from survivor to survivor 分红8:1:1 ,to survivor为保留空间;GC开始时,对象只会存在于Eden和from survivor区域,Eden全部存活的对象会复制到To survivor中,from survivor存活的对象会根据他们的年龄值决定去向,年龄超过年龄阈值(默认15),则移动到老年代,没有则移动到To survivor。
老年代采用标记/清除算法或者标记/整理算法
 
GC的标记阶段须要STW(stop the world),让全部的java线程挂起,这样JVM才能安全地标记对象
OopMap能够帮助HotSpot快速且准确完成GC Roots枚举以及肯定相关信息。
GC不是在任意位置均可以进入,只能在safepoint处才能进入,safepoint不能太少,不然gc等待的时间会好久,也不能太多,不然将增长gc的负担。
safepoint主要存放在:
1.循环的末尾
2.方法临返回前
3.调用方法的call 指令后
4.可能抛出异常的位置
 
垃圾收集器:
    若是说垃圾回收算法是内存回收的方法论,name垃圾收集器就是具体实现。JVM会结合针对不一样的场景及用户的配置使用不一样的收集器。
年轻代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old,parallel Old CMS收集器
特殊收集器:G1收集器
 
Serial:单线程、收集时必须停掉其它线程,等待收集工做完成后其它线程才能继续工做,client模式使用
ParNew收集器:serial的多线程版
Parallel Scavenge收集器:采用复制算法,支持多线程,吞吐量优先,适合后台运算
 
Serial Old:单线程,标记整理算法,client模式使用
Parallel Old:多线程,标记整理算法,吞吐量优先
CMS:多线程,支持并发,标记清除算法,最短回收停顿时间优先
它的运做分为4个阶段:
1.初始标记:标记一下GC ROOTS 能直接关联到的对象,速度很快,须要STW
2.并发标记:可达性分析
3.从新标记:修正因并发标记期间用户程序运做而产生变更的那一本部分对象的标记记录,会有些许停顿,须要STW,时间上通常 初始标记 < 从新标记 < 并发标记
4.并发清除
CMS缺点:
    1.cms性能很容易受到cpu核数的影响
    2.cms没法处理浮动垃圾(并发清除过程当中产生的新垃圾)
    3.cms采用标记清除算法,会存在垃圾碎片的问题
 
G1收集器:
G1(garbage first:尽量多的收集垃圾,避免full gc ),最短回收停顿时间优先,强化了分区,弱化了分代的概念,是区域化、增量式的收集器,它不属于新生代和老年代收集器;
用到的算法为标记-清除、复制算法,jdk1.7和jdk1.8都是默认关闭的。g1是区域化的,它将java堆划分为若干个大小相同的区域(region),jvm能够设置每一个region的大小(必须是2的幂),它会根据当前堆内存分配合理的region大小。
 
特色:
    1.不会等内存耗尽或者快耗尽的时候开始垃圾收集,而是在内部才用了启发式算法,在老年代找出具备高收集收益的分区进行垃圾收集。同时G1会根据用户设置的暂停时间目标自动调全年轻代和总堆大小,暂停目标时间越短年轻代空间就越小、总空间就越大;
    2.G1采用内存分区(region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位回收,存活的对象复制到能一个空闲分区中。因为都是以相等大小的分区为单位进行操做,所以G1自然就是一种压缩方案。
    3.G1虽然也是分代收集器,可是是逻辑上的分代概念,不存在物理上的年轻代和老年代,每一个分区可能随G1的运行在不一样代之间切换,年轻代优化分为Eden和survivor
    4.G1的收集都是STW的,每次收集可能既收集年轻代,也收集老年代
    5.region的大小在1m - 32m之间 ,必须是2的幂
   6.region分区内部又被分红了若干个大小为512byte的卡片(card),卡片会记录到全局卡片表中
   7.垃圾收集过程也分为:初始标记、并发标记、从新标记、清除
 
 
简单gc日志查看:-XX:+PrintGCDetails、jconsole 工具、jstat命令
jstat -gcutil pid:查看对应java进程gc状况
相关文章
相关标签/搜索