JVM调优
JVM调优基础
1. 先了解一些概念
JVM内存结构如下:
JVM运行时的数据区:
a.程序计数器-线程私有;b.java虚拟机栈-线程私有;c.本地方法栈-线程私有;d.java堆-线程公有;e.方法区-线程公有。
JVM堆结构如下:
java堆结构和垃圾回收如下图:
JVM堆配置参数:官方建议
1)-Xms:初始堆大小
默认物理内存的1\64(<1GB)
2) -Xmx:最大堆大小
默认物理内存的1\4(<1GB),实际中建议不大于4GB。
3)一般建议设置 -Xms=-Xmx,好处是避免每次gc后,调整堆的大小,减少系统内存分配开销。
4)整个堆大小=年轻代大小+年老代大小+持久代大小(即方法区大小)。
JVM新生代(young generation)
1)新生代=1个eden区+2个Survivor区
2)-Xmn 年轻代大小(1.4 or lator)
-XX:NewSize,-XX:MaxNewSize(设置年轻代大小(for 1.3/1.4))
默认值大小为整个堆的3/8
3) -XX:NewRatio
年轻代与年老代的比值
Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
4)-XX:SurvivorRatio
Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。
5)用来存放JVM刚分配的Java对象。
JVM老生代:
java老生代(tenured generation)
1)老年代=整个堆-年轻代大小-持久代大小
2)年轻代中经过垃圾回收没有回收掉的对象
3)老年代存储对象比年轻代年龄大的多,而且不乏大对象。
4)新建的对象也有可能直接进入老年代
a.大对象,可以通过启动参数设置-XX:PretenureSize Threshold=1024(单位为字节,默认为0)来代表超过多大时候就不在新生代分配,而是直接在老年代分配。
b.大的数组对象,切数组中无引用外部对象。
5)老年代大小无配置参数
JVM持久代(方法区) :
1)持久代=整个堆-年轻代大小-老年代大小
2)-XX:PermSize -XX:MaxPermSize
设置持久代的大小,一般情况推荐把-XX:PermSize设置成XX:MaxPermSize的值为相同的值,因为持久代大小的调整也会导致堆内存需要触发GC。
3) 存放class,Method元信息,其大小与项目的规模,类,方法的数量有关。一般设置为128M就足够,设置原则是预留30%的空间。
4)永久代的回收方式
a.常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收
b.对于无用的类进行回收,必须保证3点:类的所有实例都已经被回收;加载类的ClassLoader已经被回收;类对象的Class对象没有被引用(即没有通过反射引用该类的地方)。
一般来说,只要使用java程序和jdk,都会涉及JVM,那么涉及JVM,我的理解就是大概率会涉及JVM有关调优的问题。weblogic,tomcat,resin,jboss,and so on 哈哈。
JVM底层是用C++编写的。解决了java可以跨平台,即一次编译,到处运行。网络上说的多数是一些参数介绍和参数配比,并不是很深入。
1) 数据类型
在Java虚拟机中,数据类型分为基本类型和引用类型。基本类型保存的变量是原始的值,是在栈里运行的;引用类型保存的是变量的引用值。引用变量主要两部分组成:1)指针;2.数据本身。
a.基本类型:byte,short,int,long,char,float,double,Boolean。
b.引用类型:类类型,接口类型和数组。
注意:数组是一块连续的存储
2)堆和栈
堆和栈是程序运行的关键。栈是运行时的单位,堆是存储的单位。栈解决程序的运行问题,比如:程序如何执行,或者如何处理数据等。堆解决的是数据存储的问题,数据怎么放,放在哪里等。堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用。
在Java中,栈的大小通过-Xss来设置的。
3) 为什么要把堆和栈区分出来?栈中不是也可以存储数据吗?
2.JVM内存垃圾回收
我个人的理解是垃圾回收也是JVM调优的一个核心。
垃圾回收算法
按照基本回收策略划分:1)引用计数。(JDK1.2之前) 2)标记-清除。3)复制。4)标记-整理。
按照分区对待的方式划分:1) 增量收集。2)分代收集。
按照系统线程划分:1)串行收集。2)并行收集。3)并发收集。
3.如何分代
分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
如上图所示:虚拟机中的共划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。
1)年轻代:
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
2)年老代:
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
3)持久代:
用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。