1、JVM内存管理javascript
一张图看看:css
程序计数器是一块很小的内存空间,它是线程私有的,能够认做为当前线程的行号指示器。
同计数器也为线程私有,生命周期与相同,就是咱们平时说的栈,栈描述的是Java方法执行的内存模型。java
每一个方法被执行的时候都会建立一个栈帧用于存储局部变量表,操做栈,动态连接,方法出口等信息。每个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。python
本地方法栈是与虚拟机栈发挥的做用十分类似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,咱们打开jdk安装目录能够看到也有不少用c编写的文件,可能就是native方法所调用的c代码。c++
对于大多数应用来讲,堆是java虚拟机管理内存最大的一块内存区域,由于堆存放的对象是线程共享的,因此多线程的时候也须要同步机制。算法
堆是 JVM 所管理的最大的一块内存空间,主要用于存放各类类的实例对象。 在 Java 中,堆被划分红两个不一样的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。 这样划分的目的是为了使 JVM 可以更好的管理堆内存中的对象,包括内存的分配以及回收;数组
方法区同堆同样,是全部线程共享的内存区域,为了区分堆,又被称为非堆。ruby
用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。老版jdk,方法区也被称为永久代【由于没有强制要求方法区必须实现垃圾回收,HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器能够像管理堆区同样管理这部分区域,从而不须要专门为这部分设计垃圾回收机制。不过自从JDK7以后,Hotspot虚拟机便将运行时常量池从永久代移除了。】jdk8真正开始废弃永久代,而使用元空间(Metaspace)bash
新生代采用复制算法
老年代采用或1.标记/清除算法【最基础】 2.复制算法 3.标记/整理算法 附:
jvm采用`分代收集算法`对不一样区域采用不一样的回收算法。标记/清除算法标记/整理算法
年轻代收集器 Serial、ParNew、Parallel Scavenge 老年代收集器 Serial Old、Parallel Old、CMS收集器 特殊收集器 G1收集器[新型,不在年轻、老年代范畴内]
最基本、发展最久的收集器,在jdk3之前是gc收集器的惟一选择,Serial是单线程收集器,Serial收集器只能使用一条线程进行收集工做,在收集的时候必须得停掉其它线程,等待收集工做完成其它线程才能够继续工做。多线程
能够认为是Serial的升级版,由于它支持多线程[GC线程],并且收集算法、Stop The World、回收策略和Serial同样,就是能够有多个GC线程并发运行,它是HotSpot第一个真正意义实现并发的收集器。默认开启线程数和当前cpu数量相同【几核就是几个,超线程cpu的话就不清楚了 - -】,若是cpu核数不少不想用那么多,能够经过-XX:ParallelGCThreads来控制垃圾收集线程的数量。
采用复制算法的收集器,和ParNew同样支持多线程。
可是该收集器重点关心的是吞吐量【吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间) 若是代码运行100min垃圾收集1min,则为99%】
对于用户界面,适合使用GC停顿时间短,否则由于卡顿致使交互界面卡顿将很影响用户体验。
对于后台 高吞吐量能够高效率的利用cpu尽快完成程序运算任务,适合后台运算
和新生代的Serial同样为单线程,Serial的老年代版本,不过它采用"标记-整理算法",这个模式主要是给Client模式下的JVM使用。
若是是Server模式有两大用途
1.jdk5前和Parallel Scavenge搭配使用,jdk5前也只有这个老年代收集器能够和它搭配。
2.做为CMS收集器的后备。
支持多线程,Parallel Scavenge的老年版本,jdk6开始出现, 采用"标记-整理算法"【老年代的收集器大都采用此算法】
在jdk6之前,新生代的Parallel Scavenge只能和Serial Old配合使用【根据图,没有这个的话只剩Serial Old,而Parallel Scavenge又不能和CMS配合使用】,并且Serial Old为单线程Server模式下会拖后腿【多核cpu下没法充分利用】,这种结合并不能让应用的吞吐量最大化。
CMS收集器(Concurrent Mark Sweep)是以一种获取最短回收停顿时间为目标的收集器。【重视响应,能够带来好的用户体验,被sun称为并发低停顿收集器】
启用CMS:-XX:+UseConcMarkSweepGC
正如其名,CMS采用的是"标记-清除"(Mark Sweep)算法,并且是支持并发(Concurrent)的
它的运做分为4个阶段
1.初始标记:标记一下GC Roots能直接关联到的对象,速度很快 2.并发标记:GC Roots Tarcing过程,便可达性分析 3.从新标记:为了修正因并发标记期间用户程序运做而产生变更的那一部分对象的标记记录,会有些许停顿,时间上通常 初始标记 < 从新标记 < 并发标记 4.并发清除
以上初始标记和从新标记须要stw(停掉其它运行java线程)
之因此说CMS的用户体验好,是由于CMS收集器的内存回收工做是能够和用户线程一块儿并发执行。
整体上CMS是款优秀的收集器,可是它也有些缺点。
1.cms堆cpu特别敏感,cms运行线程和应用程序并发执行须要多核cpu,若是cpu核数多的话能够发挥它并发执行的优点,可是cms默认配置启动的时候垃圾线程数为 (cpu数量+3)/4,它的性能很容易受cpu核数影响,当cpu的数目少的时候好比说为为2核,若是这个时候cpu运算压力比较大,还要分一半给cms运做,这可能会很大程度的影响到计算机性能。
2.cms没法处理浮动垃圾,可能致使Concurrent Mode Failure(并发模式故障)而触发full GC
3.因为cms是采用"标记-清除“算法,所以就会存在垃圾碎片的问题,为了解决这个问题cms提供了 -XX:+UseCMSCompactAtFullCollection选项,这个选项至关于一个开关【默认开启】,用于CMS顶不住要进行full GC时开启内存碎片合并,内存整理的过程是没法并发的,且开启这个选项会影响性能(好比停顿时间变长)
浮动垃圾:因为cms支持运行的时候用户线程也在运行,程序运行的时候会产生新的垃圾,这里产生的垃圾就是浮动垃圾,cms没法当次处理,得等下次才能够。
G1(garbage first:尽量多收垃圾,避免full gc)收集器是当前最为前沿的收集器之一(1.7之后才开始有),同cms同样也是关注下降延迟,是用于替代cms功能更为强大的新型收集器,由于它解决了cms产生空间碎片等一系列缺陷。
g1是区域化的,它将java堆内存划分为若干个大小相同的区域【region】,jvm能够设置每一个region的大小(1-32m,大小得看堆内存大小,必须是2的幂),它会根据当前的堆内存分配合理的region大小。
g1经过并发(并行)标记阶段查找老年代存活对象,经过并行复制压缩存活对象【这样能够省出连续空间供大对象使用】。
g1将一组或多组区域中存活对象以增量并行的方式复制到不一样区域进行压缩,从而减小堆碎片,目标是尽量多回收堆空间【垃圾优先】,且尽量不超出暂停目标以达到低延迟的目的。
g1提供三种垃圾回收模式 young gc、mixed gc 和 full gc,不像其它的收集器,根据区域而不是分代,新生代老年代的对象它都能回收。
在年轻代
Young space
(包括Eden区和Survivor区)中的垃圾回收称之为 Minor GC,Minor GC只会清理年轻代.
Major GC清理老年代(old GC),可是一般也能够指和Full GC是等价,由于收集老年代的时候每每也会伴随着升级年轻代,收集整个Java堆。因此有人问的时候需问清楚它指的是full GC仍是old GC。
full gc是对新生代、老年代、永久代【jdk1.8后没有这个概念了】统一的回收。
【知乎R大的回答:收集整个堆,包括young gen、old gen、perm gen(若是存在的话)、元空间(1.8及以上)等全部部分的模式】
混合GC
收集整个young gen以及部分old gen的GC。只有G1有这个模式
1.引用计数算法 早期判断对象是否存活大多都是以这种算法,这种算法判断很简单,简单来讲就是给对象添加一个引用计数器,每当对象被引用一次就加1,引用失效时就减1。当为0的时候就判断对象不会再被引用。 优势:实现简单效率高,被普遍使用与如python何游戏脚本语言上。 缺点:难以解决循环引用的问题,就是假如两个对象互相引用已经不会再被其它其它引用,致使一直不会为0就没法进行回收。 2.可达性分析算法 目前主流的商用语言[如java、c#]采用的是可达性分析算法判断对象是否存活。这个算法有效解决了循环利用的弊端。 它的基本思路是经过一个称为“GC Roots”的对象为起始点,搜索所通过的路径称为引用链,当一个对象到GC Roots没有任何引用跟它链接则证实对象是不可用的。
minor GC(young GC):当年轻代中eden区分配满的时候触发[值得一提的是由于young GC后部分存活的对象会已到老年代(好比对象熬过15轮),因此事后old gen的占用量一般会变高] full GC: ①手动调用System.gc()方法 [增长了full GC频率,不建议使用而是让jvm本身管理内存,能够设置-XX:+ DisableExplicitGC来禁止RMI调用System.gc] ②发现perm gen(若是存在永久代的话)需分配空间但已经没有足够空间 ③老年代空间不足,好比说新生代的大对象大数组晋升到老年代就可能致使老年代空间不足。 ④CMS GC时出现Promotion Faield[pf] ⑤统计获得的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。 这个比较难理解,这是HotSpot为了不因为新生代晋升到老年代致使老年代空间不足而触发的FUll GC。 好比程序第一次触发Minor GC后,有5m的对象晋升到老年代,姑且如今平均算5m,那么下次Minor GC发生时,先判断如今老年代剩余空间大小是否超过5m,若是小于5m,则HotSpot则会触发full GC(这点挺智能的)
Promotion Faield:minor GC时 survivor space放不下[满了或对象太大],对象只能放到老年代,而老年代也放不下会致使这个错误。 Concurrent Model Failure:cms时特有的错误,由于cms时垃圾清理和用户线程能够是并发执行的,若是在清理的过程当中 可能缘由: 1 cms触发太晚,能够把XX:CMSInitiatingOccupancyFraction调小[好比-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(由于CMS会有浮动垃圾,因此通常都较早启动GC)] 2 垃圾产生速度大于清理速度,多是晋升阈值设置太小,Survivor空间小致使跑到老年代,eden区过小,存在大对象、数组对象等状况 3.空间碎片过多,能够开启空间碎片整理并合理设置周期时间
会,在初始标记的时候会扫描新生代。
虽然cms是老年代收集器,可是咱们知道年轻代的对象是能够晋升为老年代的,为了空间分配担保,仍是有必要去扫描年轻代
这样作的话效率可能会更高,可是old区通常都是熬过屡次可达性分析算法事后的存活的对象,要求比较苛刻且空间有限,而不能直接移过去,这将致使一系列问题(好比老年代容易被撑爆)
分两个Survivor(from/to),天然是为了保证复制算法运行以提升效率
对象优先分配在eden区,eden区满时会触发一次minor GC
对象晋升规则
g1再也不区分老年代、年轻代这样的内存空间,这是较以往收集器很大的差别,全部的内存空间就是一块划分为不一样子区域,每一个区域大小为1m-32m,最多支持的内存为64g左右,且因为它为了的特性适用于大内存机器
-Xms |
初始堆大小。如:-Xms256m |
-Xmx |
最大堆大小。如:-Xmx512m |
-Xmn |
新生代大小。一般为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% |
-Xss |
JDK1.5+ 每一个线程堆栈大小为 1M,通常来讲若是栈不是很深的话, 1M 是绝对够用了的。 |
-XX:NewRatio |
新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3 |
-XX:SurvivorRatio |
新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 |
-XX:PermSize |
永久代(方法区)的初始大小 |
-XX:MaxPermSize |
永久代(方法区)的最大值 |
-XX:+PrintGCDetails |
GC 信息 打印 |
-XX:+HeapDumpOnOutOfMemoryError |
让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用 |