JVM --- 堆&栈&堆参数调优

一. 方法区:

线程共享的运行时内存区域,它存储了每一个类的结构信息。什么叫类的结构信息,其实就是上一篇讲类加载器时说的类的模板。也就是类的属性、构造器、方法、常量池等。而且,方法区是一种规范,不是具体实现java7及以前的实现叫永久代java8开始,方法区的实现叫元空间。

二. 栈:

1. 栈的基本介绍:

栈也叫栈内存,主要管java程序的运行,是线程私有的。它的生命周期是跟随线程的生命周期的,线程创建时创建,线程结束栈内存就释放。栈不存在垃圾回收。8种基本类型的变量、对象的引用变量和实例方法都是在函数的栈内存中分配的。

栈帧主要保存以下3类数据(栈帧就是方法,在java代码中它叫方法,压到栈里面就叫栈帧):

本地变量:即输入参数、输出参数和方法内的变量;

栈操作:记录出栈、入栈的操作;

栈帧数据:类文件、方法等;

当你在main方法中调用另一个方法fun的时候,首先是main方法进栈,压到栈底,然后是fun方法进栈,等fun执行完,会自动将fun弹出,再继续执行main,最后main方法出栈。如果fun是一个没有终止条件的递归方法,那么就会不停地有fun入栈,直到栈装不下。此时会抛出一个错误:Exception in thread "main" java.lang.StackOverflowError。这就是栈内存溢出,注意,这是一个error,而不是exception。

2. 栈、堆、方法区的交互:

Person p1=newPerson();Person p2=newPerson();

p1、p2是引用,上面说了,引用是栈中的,new Person()是在堆中完成的,Person的模板是存在方法区的,也就是,堆中new对象的时候,用的是方法区中的模板,所以能保证new出来的两个Person实例结构都是一样的。所以栈中的p1、p2存储的是实例在堆中地址值。

三. 堆:

1.  堆基本介绍:

一个JVM实例只存在一个堆,堆的内存大小可以调节,存放的是new出来的实例和数组。堆内存逻辑上分为三部分:

新生区(新生代):占1/3的堆空间,又包括伊甸区幸存0区(S0区,from区)幸存1区(S1区,to区),这三个区的内存比例为:伊甸区 : S0区 : S1区 = 8 : 1 : 1,而且,from区和to区不是固定的,谁空谁是to;

养老区(老年代):占2/3的堆空间;

永久区(永久代)/元空间:在java7中叫永久区,java8换成了元空间,永久代是使用JVM的堆内存,而元空间是使用本机的物理内存。存放的是JDK自带的class、interface等元数据,此区域不会进行垃圾回收,关闭JVM才会释放此区域内存。

永久区/元空间是逻辑上的划分,所以物理上堆内存就是新生区 + 养老区

2. 新生区、养老区以及GC介绍:

new出来的对象首先是在伊甸区,伊甸园嘛,生命初始的地方。伊甸区对象满了的话,就会触发轻GC,即YGC。触发YGC后,幸存者将会被复制到from区,伊甸区域会被清空。当伊甸区第二次触发YGC,就会扫描伊甸区和from区,对这两个区进行垃圾回收,经过这两次垃圾回收还存活的对象,就会被复制到to区,伊甸区和form区就会被清空,并且会把这些对象的年龄加一,当年龄达到阈值(默认是15,可通过-XX:MaxTenuringThreshold配置)时,这些对象就会被复制到养老区。此时,原先的form区是空的,原先的to区存放了历经两次YGC还存活的对象。上面说了,谁空谁就是to区,所以原先的from区现在变成了to区。这就是YGC的三个过程:复制 ---> 清空 ---> 互换

如果养老区也满了,就会在养老区触发full GC,如果多次full GC还是没能腾出空间来,就会内存溢出,即OOM异常。

四. JVM调优

1. 基本介绍:

JVM调优,其实就是堆参数的调整。

jvm调优

先说一下这张图,Minor GC就是上面说的YGC,Major GC就是full GC,S0和S1区有双向箭头,表示它们不是固定的,谁空谁就是to区(S1区)。

常见堆参数:

-Xms:堆内存(新生区+养老区)的初始大小,默认为物理内存的1/64;

-Xmx:堆内存(新生区+养老区)的最大值,默认为物理内存的1/4;

-Xmn:新生区的大小

-XX:PermSize:永久代的初始值(jdk1.7)

-XX:MaxPermSize:永久代的最大值(jdk1.7)

-XX:MaxTenuringThreshold:设置对象在新生代中存活的次数,默认是15

2. 堆内存调优简介:

上面说了xms和xmx的默认大小,怎么证明呢?用下面的代码可以证明:

publicstaticvoidmain(String[]args){System.out.println(Runtime.getRuntime().availableProcessors());// cpu核数doublexms=Runtime.getRuntime().totalMemory()/1024/1024;// xmsdoublexmx=Runtime.getRuntime().maxMemory()/1024/1024;// xmxSystem.out.println("xms:"+xms+"MB");System.out.println("xmx:"+xmx+"MB");}

16G的笔记本,打印出来的xms大概是240MB,xmx大概是3600MB。为什么打印出来的更小?因为16G内存的笔记本,实际可用的内存是不到16G的。

xms和xmx,虽然一个是初始值一个最大值,但是,生产上这两个值一定要一样,为的是避免GC程序和应用程序争抢内存,导致可用内存忽高忽低;

怎么配置这两个值呢?eclipse和idea中,点击run configuration,可以配置VM arguments,将下面这串配置进去,就可以配置xms和xmx的大小,以及打印堆的信息:

-Xms1024M -Xmx1024M-XX:+PrintGCDetails

这里将xms和xmx都配置了1024,并且打印了堆信息。配置了这些再次运行上面那段代码,就会打印出如下信息(jdk1.8):

执行结果

从打印出来的信息可以发现,xms和xmx的配置生效了。从堆信息可以发现,堆确实上述由新生区、养老区和元空间构成,而且,新生区305664k加上养老区的699392k刚好等于981M,也说明了物理上堆只分为新生区和养老区,元空间是逻辑上的存在。

3. OOM异常:

上面说了,如果堆内存被占用满了,就会出现OOM异常。但是默认情况下xmx是内存的1/4,不容易出现这个异常。上面又说了配置这两个参数的方法,所以,我们可以将xms和xmx都配制成10M,然后再执行下面这段代码,就很容易出现OOM了。

String str="hello";while(true){str+=str+newRandom().nextInt(88888888)+newRandom().nextInt(99999999);}

string类型用加号拼接,其实是new一个string然后用append方法的,所以这里会一直new对象,并且用了随机数,所以基本上都不重复的对象,不会从常量池里拿到。执行这段代码后,程序报了如下的错误:

Exception in thread"main"java.lang.OutOfMemoryError:Java heap space    at java.util.Arrays.copyOfRange(Arrays.java:3664)at java.lang.String.(String.java:207)at java.lang.StringBuilder.toString(StringBuilder.java:407)

这就是OOM异常了,并且中报错之前,打印了GC相关信息,如下:

[GC (Allocation Failure)[PSYoungGen:1855K->491K(2560K)]1855K->991K(9728K),0.0024182secs][Times:user=0.00sys=0.00,real=0.00secs][GC (Allocation Failure)[PSYoungGen:2168K->272K(2560K)]2668K->1753K(9728K),0.0022543secs][Times:user=0.02sys=0.00,real=0.00secs][GC (Allocation Failure)[PSYoungGen:1621K->272K(2560K)]7032K->5682K(9728K),0.0009517secs][Times:user=0.00sys=0.00,real=0.00secs][GC (Allocation Failure)[PSYoungGen:272K->272K(2560K)]5682K->5682K(9728K),0.0007530secs][Times:user=0.00sys=0.00,real=0.00secs][Full GC (Allocation Failure)[PSYoungGen:272K->0K(2560K)][ParOldGen:5410K->3170K(7168K)]5682K->3170K(9728K),[Metaspace:2752K->2752K(1056768K)],0.0076104secs][Times:user=0.02sys=0.00,real=0.01secs][GC (Allocation Failure)[PSYoungGen:40K->32K(2560K)]5829K->5821K(9728K),0.0007024secs][Times:user=0.00sys=0.00,real=0.00secs][GC (Allocation Failure)[PSYoungGen:32K->32K(1536K)]5821K->5821K(8704K),0.0005743secs][Times:user=0.00sys=0.00,real=0.00secs][Full GC (Allocation Failure)[PSYoungGen:32K->0K(1536K)][ParOldGen:5789K->4479K(7168K)]5821K->4479K(8704K),[Metaspace:2752K->2752K(1056768K)],0.0062029secs][Times:user=0.00sys=0.00,real=0.01secs][GC (Allocation Failure)[PSYoungGen:20K->32K(2048K)]7118K->7130K(9216K),0.0007837secs][Times:user=0.00sys=0.00,real=0.00secs][Full GC (Ergonomics)[PSYoungGen:32K->0K(2048K)][ParOldGen:7098K->3170K(7168K)]7130K->3170K(9216K),[Metaspace:2752K->2752K(1056768K)],0.0054553secs][Times:user=0.03sys=0.00,real=0.01secs][GC (Allocation Failure)[PSYoungGen:20K->0K(2048K)]5809K->5789K(9216K),0.0003817secs][Times:user=0.00sys=0.00,real=0.00secs][GC (Allocation Failure)[PSYoungGen:0K->0K(2048K)]5789K->5789K(9216K),0.0003808secs][Times:user=0.00sys=0.00,real=0.00secs][Full GC (Allocation Failure)[PSYoungGen:0K->0K(2048K)][ParOldGen:5789K->5789K(7168K)]5789K->5789K(9216K),[Metaspace:2752K->2752K(1056768K)],0.0040374secs][Times:user=0.00sys=0.00,real=0.00secs][GC (Allocation Failure)[PSYoungGen:0K->0K(2048K)]5789K->5789K(9216K),0.0003072secs][Times:user=0.00sys=0.00,real=0.00secs][Full GC (Allocation Failure)[PSYoungGen:0K->0K(2048K)][ParOldGen:5789K->5775K(7168K)]5789K->5775K(9216K),[Metaspace:2752K->2752K(1056768K)],0.0075411secs][Times:user=0.01sys=0.00,real=0.01secs]

可以看到,这里先进行GC,然后是full GC,full GC也搞不定了,就报错了,这与上面讲的是一样的。那么打印的GC信息是什么意思呢?拿出一条来分析一下:

[GC (Allocation Failure)[PSYoungGen:1855K->491K(2560K)]1855K->991K(9728K),0.0011226secs][Times:user=0.00sys=0.00,real=0.00secs]

我们一行一行的看,意思分别是:

表示由于分配失败发生GC;GC发生在新生区(总内存2560K),GC之前该区用了1855K,GC之后用了491K;堆内存总共9728K,GC之前堆内存占用1855K,GC之后占用991K,本次GC总共耗时0.0011226秒;最后一行是GC时用户耗时、系统耗时和实际耗时

full GC的也是一样的:

[Full GC (Allocation Failure)[PSYoungGen:0K->0K(2048K)][ParOldGen:5789K->5775K(7168K)]5789K->5775K(9216K),[Metaspace:2752K->2752K(1056768K)],0.0075411secs][Times:user=0.01sys=0.00,real=0.01secs]

这里的意思分别是:

表示由于分配失败发生full GC;

full GC前新生区占用0k,full GC后占用0k,该区总内存2048k;

full GC之前老年区占用5789k,之后占用5775k,该区总大小7168k,full GC之前堆内存占用5789k,之后占用5575k,堆内存总大小9216k;

full GC前元空间占用2752k,之后占用2752k,元空间总大小1056768k,full GC耗时0.0075411秒;

最后一行是GC时用户耗时、系统耗时和实际耗时

作者:贪挽懒月

链接:https://www.jianshu.com/p/b2d32efe1471

来源:

你可能感兴趣的:(JVM --- 堆&栈&堆参数调优)