一、背景
1.Java虚拟机原理:所谓虚拟机,就是一台虚拟的机器,它是一款软件,用来执行一系列虚拟计算指令,大体上虚拟机可以分为系统虚拟机和程序虚拟机,大名鼎鼎的Visual Box、Vmware就属于系统虚拟机,他们完全是对物理计算的仿真,提供了一个可以运行完整操作系统的软件平台。程序虚拟机典型代表就是java虚拟机,它专门为执行单个计算程序而计算,在java虚拟机中执行的指令使我们程序成为java字节码,无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
2.首先我们要对JVM参数进行调优,那么我们就得明白java内存结构,这样我们才能更进一步的去了解JVM参数进行调优(主要调优堆和垃圾回收机制,见下图)。
二、Java内存结构(JVM虚拟机存储空间)
1.首先我们要清楚java内存模型(多线程JMM)与Java内存结构的不同(JVM虚拟机存储空间)。
2.图解(自己画的)
2.1.class文件被类加载器classloader(采用的双亲委派模式)进行加载到内存空间中去。
2.2.什么是方法区:方法区又叫永久区,存放static关键字修饰的,常量信息当然包括字符串常量信息,方法区是什么时候被执行的?当class文件被加载的时候,就会初始化,所有线程会被共享(static修饰的线程会共享的)。
2.3.调优问题:web开发的时候,定义静态常量太多好不好,不好,因为定义太多的常量它是放在方法区的,垃圾回收机制对方法区不进行回收,所以内存会越来越大,很消耗内存。
2.4.堆:创建对象、new创建数组都是存放在堆内存中的,调优主要也是对它进行调优的,所有线程是会被共享的。
2.5.栈:定义基本局部变量,也存类的方法,栈代码运行完毕,自动释放内存,每个线程私有,互不共享,栈不会产生线程安全问题。
2.6.本地方法栈:主要调用底层C语言的(了解)。
三、详细讲堆内存
1.图解
1.1.什么是堆内存:创建对象、new创建数组都是存放在堆内存中的。
1.2.堆内存中又分为两个区:新生代、老年代,为啥这么分,主要是为了垃圾回收机制。
1.3.新生代又分为三个区:eden、s0、s1。
1.4.新生代:刚创建的对象,先放在新生代的eden区,比如user对象新建,先放在eden区,如果这个对象被频繁的使用,就会晋升到s0或者是s1区(s0、s1目的是为了垃圾回收机制的复制算法),如果在s0或者s1区中还是在频繁的使用,这时候就会晋升到老年代中。
1.5.老年代:如果对象在频繁的使用,对象放入到老年代。
1.6.分代的目的就是为了垃圾回收机制,垃圾回收机制主要回收新生代的对象,老年代也会回收不过次数很少的,还有一点就是老年代的对象不会逆流到新生代中去。
1.7.调优:在web系统中,尽量减少垃圾回收机制的次数,因为如果频繁的话,服务的其他线程都会被卡死,很影响效率的,所以不要频繁回收垃圾回收。
还有一个web开发的时候,定义静态常量太多好不好,不好,因为定义太多的常量它是放在方法区的,垃圾回收机制对方法区不进行回收,所以内存会越来越大,很消耗内存。
新生代回收次数要比老年代次数多。
四、堆内存参数配置
1.什么是虚拟机参数配置
在虚拟机运行的过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助,为此,在虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行Java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。我们进行虚拟机参数配置,其实就是围绕着堆、栈、方法区、进行配置。你说下 你熟悉那些jvm参数调优。
2.堆的参数配置
-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空间的比例.
五、设置最大堆内存
1.代码栗子:
/**
* Created by ChenMingXu on 2019/5/26.
*/
public class demo001 {
public static void main(String[] args) throws InterruptedException {
byte[] bytes01 = new byte[1 * 1024 * 1024];
System.out.println("分配了1M内存");
jvmInfo();
Thread.sleep(3000);
byte[] bytes02 = new byte[4 * 1024 * 1024];
System.out.println("分配了4M内存");
jvmInfo();
}
static private String toM(long maxMemory) {
float num = (float) maxMemory / (1024 * 1024);
DecimalFormat df = new DecimalFormat("0.00");// 格式化小数
String s = df.format(num);// 返回的是String类型
return s;
}
public static void jvmInfo() {
// 最大内存配置信息kb
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("maxMemory:" + maxMemory + "," + toM(maxMemory) + "M");
// 当前空闲内存
long freeMemory = Runtime.getRuntime().freeMemory();
System.out.println("freeMemory:" + freeMemory + "," + toM(freeMemory) + "M");
//已使用内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("totalMemory:" + totalMemory + "," + toM(totalMemory) + "M");
}
}
2.结果:
分配了1M内存
maxMemory:1879048192,1792.00M //默认堆内存大小
freeMemory:121798192,116.16M
totalMemory:126877696,121.00M
分配了4M内存
maxMemory:1879048192,1792.00M
freeMemory:117603872,112.16M
totalMemory:126877696,121.00M //可能因为底层打印太快了,具体再加上4M
3.调优参数
3.1.参数: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags,// 堆初始值、堆最大可用值、串行回收、每次触发GC的时候打印相关日志,目的减少垃圾回收的次数已达到调优的目的。
3.2.怎么使用呢?
3.3.日志打印分析
3.3.1.初始值设置成5M、最大值设置成20M,这时候垃圾回收2次。
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
[GC (Allocation Failure) [DefNew: 1664K->192K(1856K), 0.0389905 secs] 1664K->671K(5952K), 0.1851062 secs] [Times: user=0.00 sys=0.00, real=0.18 secs]
分配了1M内存
[GC (Allocation Failure) [DefNew: 1856K->106K(1856K), 0.0023211 secs] 2335K->1799K(5952K), 0.0023404 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
maxMemory:20316160,19.38M
freeMemory:3889352,3.71M
totalMemory:6094848,5.81M
[GC (Allocation Failure) [DefNew: 491K->134K(1856K), 0.0029878 secs][Tenured: 1797K->1931K(4096K), 0.0073899 secs] 2184K->1931K(5952K), [Metaspace: 3934K->3934K(1056768K)], 0.1023465 secs] [Times: user=0.02 sys=0.00, real=0.10 secs]
分配了4M内存
maxMemory:20316160,19.38M
freeMemory:4153352,3.96M
totalMemory:10358784,9.88M
Heap
def new generation total 1920K, used 49K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
eden space 1728K, 2% used [0x00000000fec00000, 0x00000000fec0c6a8, 0x00000000fedb0000)
from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
tenured generation total 8196K, used 6027K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
the space 8196K, 73% used [0x00000000ff2a0000, 0x00000000ff882fe0, 0x00000000ff883000, 0x00000000ffaa1000)
Metaspace used 3941K, capacity 4646K, committed 4864K, reserved 1056768K
class space used 435K, capacity 462K, committed 512K, reserved 1048576K
3.3.2.初始值设置成20M、最大值设置成20M,这时候垃圾回收1次,这样能达到调优的效果。
-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
分配了1M内存
maxMemory:20316160,19.38M
freeMemory:16335496,15.58M
totalMemory:20316160,19.38M
[GC (Allocation Failure) [DefNew: 3887K->640K(6144K), 0.0076749 secs] 3887K->1931K(19840K), 0.0077995 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
分配了4M内存
maxMemory:20316160,19.38M
freeMemory:14039648,13.39M
totalMemory:20316160,19.38M
Heap
def new generation total 6144K, used 4893K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000)
eden space 5504K, 77% used [0x00000000fec00000, 0x00000000ff0274f8, 0x00000000ff160000)
from space 640K, 100% used [0x00000000ff200000, 0x00000000ff2a0000, 0x00000000ff2a0000)
to space 640K, 0% used [0x00000000ff160000, 0x00000000ff160000, 0x00000000ff200000)
tenured generation total 13696K, used 1291K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000)
the space 13696K, 9% used [0x00000000ff2a0000, 0x00000000ff3e2e48, 0x00000000ff3e3000, 0x0000000100000000)
Metaspace used 3942K, capacity 4646K, committed 4864K, reserved 1056768K
class space used 435K, capacity 462K, committed 512K, reserved 1048576K
六、配置新生代与老年代调优参数
1.设置新生代与老年代回收比例,jvm参数调优,怎么样让垃圾回收机制经常去新生代进行回收,新生代与老年代1:3或者1:4的比例进行分配
2.代码演示(设置新生代比例参数例子,配置新生代的eden与s0或者s1的比例关系)
/**
* Created by ChenMingXu on 2019/5/27.
*/
public class demo002 {
public static void main(String[] args) {
//调优参数
//-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC //配置新生代的eden与s0或者s1的比例关系
byte[] bytes = null;
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
bytes = new byte[1 * 1024 * 1024];
}
}
}
2.1.调优参数:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
解释:-Xmn 新生代大小值,一般设为整个堆的1/3到1/4左右。
-XX:SurvivorRatio 设置新生代中eden区和from/to(s0和s1区)空间的比例关系n/1
2.2.运行结果
[GC (Allocation Failure) [DefNew: 506K->256K(768K), 0.0008095 secs] 506K->430K(20224K), 0.0008442 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 768K->169K(768K), 0.0010041 secs] 942K->598K(20224K), 0.0010229 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 681K->58K(768K), 0.0005780 secs] 1110K->655K(20224K), 0.0005968 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
i:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
i:8
i:9
Heap
def new generation total 768K, used 514K [0x00000000fec00000, 0x00000000fed00000, 0x00000000fed00000)
eden space 512K, 89% used [0x00000000fec00000, 0x00000000fec72008, 0x00000000fec80000)
from space 256K, 22% used [0x00000000fecc0000, 0x00000000fecce978, 0x00000000fed00000)
to space 256K, 0% used [0x00000000fec80000, 0x00000000fec80000, 0x00000000fecc0000)
tenured generation total 19456K, used 10837K [0x00000000fed00000, 0x0000000100000000, 0x0000000100000000)
the space 19456K, 55% used [0x00000000fed00000, 0x00000000ff795490, 0x00000000ff795600, 0x0000000100000000)
Metaspace used 3443K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 374K, capacity 388K, committed 512K, reserved 1048576K
2.3.运行结果分析
此时新生代中eden区和from/to(s0和s1区)空间的比例关系2:1,即:(256+256)=512k
3.代码演示(设置新生代与老年代比例参数例子)
/**
* Created by ChenMingXu on 2019/5/27.
*/
public class demo002 {
public static void main(String[] args) {
//调优参数
//-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2 //配置新生代与老年代的关系
byte[] bytes = null;
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
bytes = new byte[1 * 1024 * 1024];
}
}
}
3.1.调优参数:-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2 //配置新生代与老年代的关系(1:2),在实际中尽量设置(1:3或者1:4).
总结:不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数。除了可以设置新生代的绝对大小(-Xmn),可以使用(-XX:NewRatio)设置新生代和老年代的比例:-XX:NewRatio=老年代/新生代
3.2.运行结果
i:0
i:1
[GC (Allocation Failure) [DefNew: 3095K->1664K(5120K), 0.0027022 secs] 3095K->1744K(18816K), 0.0027460 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
i:2
i:3
i:4
[GC (Allocation Failure) [DefNew: 4800K->1024K(5120K), 0.0019211 secs] 4880K->1744K(18816K), 0.0019513 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
i:5
i:6
i:7
[GC (Allocation Failure) [DefNew: 4161K->1024K(5120K), 0.0007788 secs] 4881K->1744K(18816K), 0.0008016 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
i:8
i:9
Heap
def new generation total 5120K, used 4231K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000)
eden space 3456K, 92% used [0x00000000fec00000, 0x00000000fef21f98, 0x00000000fef60000)
from space 1664K, 61% used [0x00000000ff100000, 0x00000000ff200010, 0x00000000ff2a0000)
to space 1664K, 0% used [0x00000000fef60000, 0x00000000fef60000, 0x00000000ff100000)
tenured generation total 13696K, used 720K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000)
the space 13696K, 5% used [0x00000000ff2a0000, 0x00000000ff354040, 0x00000000ff354200, 0x0000000100000000)
Metaspace used 3440K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 374K, capacity 388K, committed 512K, reserved 1048576K
3.3.运行结果分析:新生代:老年代=1:2、(3456+1664+1664)x2=13568k,约等于老年代的内存大小13696k
七、堆溢出解决方法
1.代码例子
/**
* Created by ChenMingXu on 2019/5/27.
*/
public class demo003 {
public static void main(String[] args) {
List<Object> listObject = new ArrayList<>();
for (int i = 0; i < 10; i++) {
System.out.println("i:"+i);
listObject.add(new byte[1*1024*1024]);
}
System.out.println("创建完毕!");
}
}
2.参数设置:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError(因为listObject也要占内存,所以会报堆内存溢出):
i:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14788.hprof ...
Heap dump file created [8977694 bytes in 0.045 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at demo003.main(demo003.java:12)
3.解决办法参数设置:-Xms10m -Xmx40m -XX:+HeapDumpOnOutOfMemoryError(实际也是修改-Xmx的大小一般为30m或者40m)。
3.1.打印结果
i:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
i:8
i:9
创建完毕!
八、栈溢出解决方法
1.代码演示
/**
* Created by ChenMingXu on 2019/5/27.
*/
//什么是栈溢出? 无线递归调用
public class demo004 {
private static int count = 0;
public static void getCount() {
try {
count++;
getCount();
} catch ( Throwable e) {
System.out.println("最大的深度...." + count);
e.printStackTrace();
}
}
//栈溢出 是方法中递归调用 不是循环调用方法 发生栈溢出
public static void main(String[] args) {
// for (int i = 0; i < 10000; i++) {
getCount();
}
}
2.结果显示(栈溢出了错误)
java.lang.StackOverflowError
最大的深度....13483
at java.util.HashMap.afterNodeAccess(HashMap.java:1779)
at java.util.HashMap.putVal(HashMap.java:657)
at java.util.HashMap.put(HashMap.java:612)
at java.util.HashSet.add(HashSet.java:220)
at java.util.Collections$SynchronizedCollection.add(Collections.java:2035)
at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:508)
at demo004.getCount(demo004.java:11)
at demo004.getCount(demo004.java:11)
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 876
3.解决办法
3.1.栈溢出产生于递归调用,循环遍历是不会的,但是循环方法里面产生递归调用, 也会发生栈溢出。
3.2.解决办法:设置线程最大调用深度,-Xss5m 设置最大调用深度
3.3.打印结果(最大深度比上面的大了很多了)
java.lang.StackOverflowError
最大的深度....274094
at java.util.HashMap.afterNodeAccess(HashMap.java:1779)
at java.util.HashMap.putVal(HashMap.java:657)
at java.util.HashMap.put(HashMap.java:612)
at java.util.HashSet.add(HashSet.java:220)
at java.util.Collections$SynchronizedCollection.add(Collections.java:2035)
at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:508)
at demo004.getCount(demo004.java:11)
at demo004.getCount(demo004.java:11)
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 876
九、结束
以上就是我对jvm调优的学习以及认识,如有哪里做的不好,请批评指正,共勉!!!
Always keep the faith!!!