java虚拟机在应用在执行的过程中将自己管理的内存分为5部分: 方法区,堆,虚拟机栈,本地方法栈,程序计数器
程序计数器:是线程私有的 表示代码执行到哪里,通过改变这个计数器的值来选取下一条需要执行的字节码指令,该内存是唯一一个不会发生内存溢出的地方如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。
虚拟机栈:就是我们平时说的堆栈中的栈,是线程私有的,表示的是方法的内存模型,保存着局部变量表,操作数栈,方法出口等信息 ;在执行每个方法时会创建栈帧,方法的执行就是栈帧的入栈和出栈。
本地方法栈:略
堆:堆内存是我们比较关心的,它是gc的主要区域,是线程共享的,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。可以通过-Xmx和-Xms控制此内存的大小。
方法区:方法区又称永久代,非堆,此区域保存的是类信息、常量、静态变量,是线程共享的
常量池:常量池是方法区的一部分,存放各种字面量和符号引用
java对象的创建 java程序在运行的时候无时无刻都在创建对象,那么他是怎么创建的呢?(这里我们只讨论普通对象new)
当虚拟机遇到new指令的时候会检测要创建的对象在常量池里面有没有符号引用,并检查改符号引用对应的类被加载、解析和初始化过没有,如果没有就进行类加载过程(后续再讲)。 类加载后就要为新的对象分配内存,对象的大小在类加载完成就已经确定了,即可在堆内存划分出一块改对象的区域。
假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。
如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
GC的过程
老年代使用标记整理,新生代使用复制算法 Full GC:发生在老年代+新生代的垃圾回收 Minor GC:发生在新生代的垃圾回收 Major GC:发生在老年代的垃圾回收,但是经常伴随了至少一次的Minor GC(不是绝对),所以经常来说Major GC和Full GC是等价的
对象在Eden Space创建,当Eden Space满了的时候,gc就把所有在Eden Space中的对象扫描一次,把所有有效的对象复制到第一个Survivor Space,同时把无效的对象所占用的空间释放。当Eden Space再次变满了的时候,就启动移动程序把Eden Space中有效的对象复制到第二个Survivor Space,同时,也将第一个Survivor Space中的有效对象复制到第二个Survivor Space。如果填充到第二个Survivor Space中的有效对象被第一个Survivor Space或Eden Space中的对象引用,那么这些对象就是长期存在的,此时这些对象将被复制到Permanent Generation。若垃圾收集器依据这种小幅度的调整收集不能腾出足够的空间,就会运行Full GC,此时JVM GC停止所有在堆中运行的线程并执行清除动作。
一次 Minor GC的过程
什么对象进入老年代? 大对象直接进入老年代(大小可以设置) 长期存活的进入老年代(年龄可以设置,动态年龄)
Minor GC 新生代GC,指发生在新生代的垃圾收集动作,使用复制算法,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。
在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
串行GC 在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。
并行回收GC 在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。
并行GC 与老年代的并发GC配合使用。
老年代的GC(Major GC/Full GC): 老年代与新生代不同,老年代对象存活的时间比较长、比较稳定,因此采用标记(Mark)算法来进行回收
在执行机制上JVM提供了串行GC(Serial MSC)、并行GC(Parallel MSC)和并发GC(CMS)。
串行GC(Serial MSC)
client模式下的默认GC方式,可通过-XX:+UseSerialGC强制指定。每次进行全部回收,进行Compact,非常耗费时间。
并行GC(Parallel MSC)(吞吐量大,但是GC的时候响应很慢)
server模式下的默认GC方式,也可用-XX:+UseParallelGC=强制指定。可以在选项后加等号来制定并行的线程数。
并发GC(CMS)(响应比并行gc快很多,但是牺牲了一定的吞吐量)
使用CMS是为了减少GC执行时的停顿时间,垃圾回收线程和应用线程同时执行,可以使用-XX:+UseConcMarkSweepGC=指定使用,后边接等号指定并发线程数。CMS每次回收只停顿很短的时间,分别在开始的时候(Initial Marking),和中间(Final Marking)的时候,第二次时间略长。CMS一个比较大的问题是碎片和浮动垃圾问题(Floating Gabage)。碎片是由于CMS默认不对内存进行Compact所致,可以通过-XX:+UseCMSCompactAtFullCollection。
理解GC日志
-XX:+PrintGCDetails:用于告诉虚拟机回收垃圾的时候顺便打印日志
-Xloggc:路径 :将打印出来的日志信息保存至指定的路径
-Xmn10M:设置新生代的内存大小
-XX:SurvivorRatio=8:1调整Eden和Survivor的比例为8:1
用参数-Xms20m -Xmx20m -Xmn10
-XX:+PrintGCDetails -Xloggc:d:\gc1.log启动。表示给堆分配20M,给新生
代分配10M,打印GC日志,并将其输出至D盘的gc1.log文件中。
)0.090:就是虚拟机从启动到现在经历的时间。
2)GC:指的是停顿类型(留着下一章讲)
3)PSYoungGen:发生GC的区域,这里指的是年轻代。根据收集器的种类而定。
4)7284K->1016K(9216K):该区域GC前当前区域所使用的容量–>该区域GC后已使用的容量(该区域的总容量),也就是新生代的容量。
5)7284K->6139K(19456K):整个堆GC前已使用的容量–>整个堆GC后已使用的容量(整个堆的总容量)。
6) 0.0078481:这次GC所占用的时间。
看图画红线部分,表示当前的堆中新生代可用内存的大小(一个eden和一个Survivor视为可用内存)
红色框的部分则是新生代中eden区和两个Survivor区的大小,可以看出他们的比例是8:1,如果设置为-XX:SurvivorRatio=3的话,结果如下
eden space 6144K
from space 2048K
to space 2048K