JVM内存分配及GC
内存区域划分
虚拟机栈
线程私有,每个方法会创建一个栈帧,栈帧中存放局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息;
本地方法栈
程序计数器
每个线程都有自己的程序计数器。执行JVM,寄存器中保存当前执行指令的地址;执行native方法,寄存器为空;
堆
所有的对象和数组都在堆上分配,GC区域;
方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等;
类信息包含类的版本、字段、方法、接口等信息;
注:
JDK 1.7 和 1.8 将运行时常量池由永久代转移到堆中;
JDK 1.7中类的静态变量(class statics)转移到了java heap;符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
1)所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
2) 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中.常说的常量池,就是指方法区中的运行时常量池。运行时常量池中还会包括直接引用。
对象的创建
检查能否在常量池中定位到一个类的符号引用,否则执行相应的类加载过程;
分配内存:指针碰撞,空闲列表;参数-XX:+/-UseTLAB;
将分配的内存空间都初始化为零值。
对象的内存布局
内存布局分为:对象头Header,实例数据Instance Data,对齐填充Padding.
Header包括存储对象自身的运行时数据(Mark Word,包括HashCode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳),类型指针,一块记录数组长度的数据(对象是Java数组的话)。
对象的访问定位
句柄:栈上的reference中存储的是对象的句柄地址,句柄中包括了对象实例数据与类型数据各自的具体地址信息;
直接指针:栈上的reference中存储的是对象地址,需考虑如何放置访问类型数据的相关信息.速度快,节省了一次指针定位的时间开销,Sun HotSpot使用该方式。
对象是否存活
引用计数算法
无法解决对象互相引用的问题。
可达性分析算法
以"GC Roots"的对象作为起始点,搜索所走过的路径即引用链,当一个对象到GC Roots无任何引用引用链相连,则此对象不可用.
可作为GC Roots的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;本地方法栈中JNI引用的对象。
对象是否死亡
如果对象在进行可行性分析后发现没有与GC Roots相连接的引用链,那它将被第一次标记并进行一次筛选,条件是没有覆盖finalize()方法或已经被虚拟机调用过finalize()方法.
如果该对象有必要执行finalize()方法,该对象将被放置在一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的,低优先级的Finalizer线程去执行.注意,这里的执行是指虚拟机会触发这个方法,但并不会承诺会等待它运行结束。
回收方法区
废弃常量
无用的类是指:该类所有的实例都已被回收;加载该类的ClassLoader已经被回收;该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方案通过反射访问该类的方法。
内存分配策略
对象优先在Eden分配
大对象直接进入老年代
长期存活的对象将进入老年代
-XX:MaxTenuringThreshold
动态对象年龄判定
Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
空间分配担保
-XX:HandlePromotionFailure
Minor GC前,虚拟机会检查老年代最大可用连续空间是否大于新生代所有对象总空间,成立则该Minor GC安全;
否则查看HandlePromotionFailure是否为true,成立则检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,若大于,将冒险进行一次Minor GC;
否则HandlePromotionFailure为false,或小于历次平均大小,则必须进行Full GC。
引用类型
强引用(Strong Reference): 只要强引用还存在,GC永远不会回收掉被引用的对象.如:Object obj = new Object();
软引用(Soft Reference): 有用但并非必需的对象,在将内存溢出时,会把这些对象列进回收范围中进行第二次回收.SoftReference类实现软引用;
弱引用(Weak Reference): 比软引用更弱一些的非必需对象,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,GC时,无论内存是否足够,都会回收掉只被弱引用关联的对象.WeakReference类实现弱引用;
虚引用(Phantom Reference): 最弱的一种引用.一个对象是否有虚引用的存在完全不会对齐生存时间构成影响,也无法通过虚引用来取得一个对象实例.为一个对象设置虚引用关联的唯一目的就是该对象被回收时收到一个系统通知.PhantomReference类来实现虚引用。
垃圾收集算法
标记-清除算法
效率问题,空间问题(内存碎片)
复制算法
实现简单,运行高效;代价是内存缩小为一半
标记-整理算法
针对老年代
分代收集算法
垃圾收集器
Serial
单线程收集器,必须暂停其他所有的工作线程;
优点是简单而高效,适合运行在Client模式下的虚拟机的默认新生代收集器。
ParNew
Serial的多线程版,其余与Serial一致;
适合运行在Server模式下的虚拟机中首选的新生代收集器,只能与CMS收集器配合工作。
Parallel Scavenge
使用复制算法的新生代收集器;
吞吐量优先,高吞吐量可高效率地利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务;相关参数:-XX:MaxGCPauseMillis,-XX:GCTimeRatio;
可自适应调节,将内存管理的调优任务交给虚拟机去完成,设置开关参数-XX:+UseAdaptiveSizePolicy。
Serial Old
Serial的老年代版本,单线程收集器,使用标记-整理算法;
主要意义在于Clinet模式下的虚拟机;
用途: JDK1.5及之前与Parallel Scavenge搭配,CMS的后续预案。
Parallel Old
Parallel Scavenge的老年代版,使用多线程和标记-整理算法;
注重吞吐量和CPU资源敏感的场合,都可以考虑Parallel Scavenge加Parallel Old收集器。
CMS
以获取最短回收停顿时间为目标的收集器,适合重视服务的响应速度,希望系统停顿时间最短的应用(优点,低停顿);
使用标记-清除算法,过程:初始标记(停顿),并发标记,重新标记(停顿),并发清除;
总体上,CMS是与用户线程一起并发执行的(优点:并发收集);
缺点:对CPU资源非常敏感,无法处理浮动垃圾(CMS运行时预留内存无法满足需要,会出现"Concurrent Mode Failure",从而临时使用Serial Old进行老年代GC),可能产生大量空间碎片。
G1
优点:
并行与并发:使用多个CPU缩短Stop-The-World停顿的时间;
分代收集:不需要其他收集器配合;
空间整合:整体上基于标记-整理,局部(两个Region)上市基于复制算法,因此不会产生内存空间碎片;
可预测的停顿:相对于CMS的优势,除了追求低停顿,还能建立可预测的停顿时间模型,让使用者明确指定在M毫秒内,垃圾收集的时间不超过N毫秒。
注:G1将内存化整为零,Region之间的对象引用及其他收集器中的新生代和老年代之间的对象引用,虚拟机都是使用Remebered Set来避免全堆扫描的。
G1 Young GC
只需要选定young代region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation
G1 Mixed GC
old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。
三色标记法
CMS:增量更新(Write Barrier时,只要发现把一个白色的引用赋值给黑色的引用,就把这个白色的引用置灰)
G1:STAB(删除的时候记录所有的对象)缺点:可能存在浮动垃圾
Card Table与Remembered Set
对象分配在哪里
除了在堆中分配,
还可以在栈中分配(逃逸分析,标量替换)
以及在TLAB(Thread Local Allocation Buffer,即线程本地分配缓存区,为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配,我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间不再需要进行任何的同步)中。
对象分配的两种方法
指针碰撞
空闲列表
minor gc的触发条件
eden满了
full gc的触发条件
升到老年代的对象大于老年代剩余空间;
Minor GC前,检查老年代最大可用连续空间是否大于新生代所有对象总空间;
若成立,则该Minor GC是安全的;
若不成立,检查HandlePromotionFailure是否允许分配担保失败;
若允许且老年代最大可用的连续空间大于历次晋升到老年带对象的平均大小,若大于,则尝试进行一次Minor GC(有风险);
若不允许,或者老年代最大可用的连续空间小于历次晋升到老年代对象的平均大小,则进行Full GC。
OOM的触发条件
gc与非gc时间耗时超过了GCTimeRatio的限制
什么样的对象会被GC
从gc root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象。
gc做了什么
新生代复制算法,
老年代的标记整理算法,标记清除算法(产生的碎片是否需要整理)
各种GC算法的优劣
JDK命令行工具
jps
-q 只输出LVMID
-m 输出启动时的main()参数
-l 输出主类全名
-v 输出jvm启动参数
jstat
-gc 监视java堆状况,包括了Eden区,两个survivor区,老年代,永久带等的容量,已用空间,gc时间合计等信息
-gccapacity 同gc一致,主要关注Java各区域使用的最大,最小空间
-gcutil 同gc一致,主要关注已使用空间占总空间的百分比
-gccause 同gcutil一致,会额外输出导致上一次GC的原因
-gcnew 监视新生代GC状况
-gcnewcapacity 同gcnew一致,主要关注使用到的最大,最小空间
-gcold 监视老年代GC状况
-gcoldcapacity 同-gcold一致,主要关注使用到的最大,最小空间
-gcpermcapacity 监视永久代使用的最大最小空间(1.8中已删除)
-gcmetacapacity
-class 监视类加载,卸载数量,总空间及类加载耗时
-compiler 输出JIT编译器编译过的方法,耗时等信息
-printcompilation 输出已经被JIT编译的方法
jinfo
-sysprops to print Java system properties
-flags to print VM flags
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
jmap
-heap 显示Java堆详细信息,包括回收器,参数配置,分代状况
-histo 显示对中对象统计信息,包括类,实例数量,合计容量
-clstats to print class loader statistics
-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象
-dump 生成Java堆转储快照
-F 强制生成dump快照
-J<flag> to pass <flag> directly to the runtime system
jhat
与jmap搭配使用,来分析jmap生成的堆转储快照
jstack
生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件,就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,可定位线程出现长时间停顿的原因,如线程间死锁,死循环,请求外部资源导致的长时间等待等)
-F 当正常输出的请求不被相应时,强制输出线程堆栈
-l 除堆栈外,显示关于锁的附加信息
-m 如果调用到本地方法的话,可以显示C/C++的堆栈
jconsole
jvisualvm
功能最强大的运行监视和故障处理程序,还提供了很多其他方面的功能,如性能分析.有一个很大的优点:不需要被监视的程序基于特殊Agent运行,因此它对应用程序的实际性能影响很小,使得它可以直接应用在生产环境中。
jvm参数
1 ) -Xint 指令
int是interpretation的简称 翻译解释的意思 意味着强制JVM执行所有的字节码 这会降低运行速度[10倍左右]
2 ) -Xcomp 指令
comp是Compile的简称 编译的意思 意味着JVM在第一次使用时会把所有的字节码编译成本地代码 从而带来最大程度的优化,虽然比-Xint的效率要高,但是它没有让JVM启动JIT编译器的全部功能, JIT编译器一般会在运行时创建方法使用文件 然后一步步的优化每个方法,因此该指令还是会造成一定的效率衰减
3 ) -Xmixed 指令
JVM在运行时可以动态的把字节码编译成本地代码
默认开启了混合模式,因此无需显示的指定