java虚拟机的内存结构:以一个Class文件在虚拟机中加载为例解释在虚拟机中的分布,当一个被编译的.java文件被编译成.class文件时,在虚拟机内部有一个类加载器系统会将该类里面的全部信息分布到相关的区域,在虚拟机内部运行时数据区域的划分为:
方法区-》虚拟机栈-》本地方法栈-》堆
Java虚拟机栈:描述Java方法执行的内存模型,用于存储局部变量表,操作数栈,动态链接,方法出口,方法从调用到完成意味着入栈和出栈。
本地方法栈:与虚拟机栈的作用类似,区别是虚拟机栈执行的是Java字节码,本地方法栈是使用Native方法服务
Java堆:所有线程共享的内存区域,在虚拟机启动时创建,此内存区域唯一的目的是存放对象实例,是垃圾收集器管理的主要区域。具体对堆的内存进行分层,是可以分为新生代和老年代的。在新生代区又可以分为eden区,To survivor(S1),Form survivor(S2)。
方法区:是各线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,以及编译器编译后的代码等数据。
运行时常量池:是方法区的一部分,用于存放编译期生成的字面量的符号引用,这部分将在类加载进入方法区的运行时常量池中存放。
虚拟机参数调优:
-XX:+PrintGC 每次触发GC的时候打印相关日志
-XX:+UseSerialGC 串行回收
-XX:+PrintGCDetails 更详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
含以-XX:SurvivorRatio=eden/from=den/to
总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,
这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
以上是最常见的相关参数,进行参数的解释:
public static void main(String[] args) throws InterruptedException {
byte[] b1 = new byte[1 * 1024 * 1024];
System.out.println("分配了1m");
jvmInfo();
Thread.sleep(3000);
byte[] b2 = new byte[4 * 1024 * 1024];
System.out.println("分配了4m");
Thread.sleep(3000);
jvmInfo();
}
但选择使用参数:-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags时打印结果为:
发现当设置初始值为5M最大值为20M时发生了一次垃圾回收。
当把初始值设置为20M和最大值一致时发现结果为:没有进行垃圾回收
结论: 尽量将初始值设置的大一些可以降低JVM的垃圾回收次数。
现在来观察将新生代和来年代的参数进行设置看看:
当设置为:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
发现参数新生代总数和老年代的数量的比值为1:2和参数 SurvivorRatio=2 形成对照
结论:如果要实现对新生代的垃圾回收需要将新生代和老年代的比例置为1:2或者1:3,这样就可以使垃圾回收去回收新生代
总结论:虚拟机参数调优是调节虚拟机垃圾回收的次数,为什么要调节垃圾回收的次数是因为频繁的调用垃圾回收会降低其性能,因为JVM进行垃圾回收的时候需要暂停所以的线程进行回收(暂停是因为防止线程又再次创建出对象,使得回收的不干净)这样会产生停顿,如果在web端大量的停顿会对用户带来不好的体验;其次是调节新生代和老年代的比例参数,一般是1:2或者是1:3,因为如果新生代的比例大了的话意味着垃圾回收会去回收老年代的对象,这无疑是加大了垃圾回收的时间,而当老年代的比例调大了的话无疑是加大了垃圾回收对新生代的GC次数;
分析下对象在堆中的进行何种GC回收 :当New一个对象,该对象优先分配在Eden,如果Eden区没有足够的空间则执行Minor GC,每次执行一次Minor GC生存下来的对象保存到S1或者S2区,并给该对象年龄参数设置为一,当达到阈值时该对象将进入老年代。如果是大对象则直接进入老年代。如果S区同年龄的存储大于S区的一半时大于或等于该年龄的对象将进入老年代。当执行MinorGC时,虚拟机会计算S区移动到老年代的平均大小,如果该大小大于老年代的剩余值时则进行FullGC否则检查参数,如果为True则执行MonorGC,为false则FullGC。
虚拟机算法分析:
判断对象是否存活:
引用计数算法:给对象中添加一个引用计数器(默认为15),当每次执行GC时会判断该对象是否可达,如果不可达则减一,当为可达时加一,当该计数器为0时该对象就是不可能在被使用了,当该对象到达阈值时进入老年代。该算法有一个缺陷,如果两个对象互相引用(比如父引用子,子引用父)那么他们的引用计数就不会为0也就是说不会被回收
可达性分析算法:通过一系列的被称为GC Root的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为引用链,当对象到GC Root没有任何引用链相连接则证明该对象不可用。JAVA中可以作为GC Root的对象有:虚拟机栈中引用的对象、方法区中的静态属性的对象、方法区中常量引用的对象、本地方法栈中JIN即Native方法引用的对象
备注:对象在被垃圾回收前有自我拯救的机会,方法就是使用finalize(),但是不建议使用该方法来对对象进行拯救。该方法是在垃圾回收之前调用的
垃圾回收算法:
标记-清除算法:标记出所有要回收的对象,在标记完成后统一回收所有被标记的对象,该算法的缺陷就是效率问题还有就是碎片化。该算法一般不使用,使用也是使用标记-整理算法
复制算法:将可用内存容量划分成相等的俩块(在新生代有这么俩块叫To survivor,Form survivor)每次只使用其中一块,当其中一块内存用完就将存活对象复制到另一块然后将使用过的空间一次性清理。该算法的缺陷:将内存缩小到原来的一半,对象存活率高时效率低。该算法适合新生代
标记-整理算法:是标记-清除算法升级版,该算法则不是直接对对象进行回收,而是将存活的对象都向一端移动,然后对边界以外的内存直接清理掉。该算法是适合老年代的
分代收集算法:根据对象存活周期的不同来划分内存区域,一般将之划分为新生代和老年代。其中新生代中有大批对象的死去,只有少量的对象存活采用复制算法;老年代存活率高,没有额外的空间分配担保采用标记-整理算法。
垃圾收集器:
①:Serial初代1.3时采用的单线程收集器
②:ParNew:Serial收集器的多线程版本(Server moder下首选新生代收集器)
③:Parallel seavenge:和 ParNew一样但是唯一的区别是GC自适应调节策略(吞吐量)
④:Serial Old:老年代版本收集器,用于Client模式
⑤:Parallel Old:Parallel Seavenge的老年代版本与吞吐量收集器配合
⑥:CMS;获取最短回收停顿时间为目标,集中于互联网站式或B/S系统服务器
⑦:G1:并行并发,分代收集,空间整合,可预测的停顿(测试阶段)