一. 方法区:
线程共享的运行时内存区域,它存储了每一个类的结构信息。什么叫类的结构信息,其实就是上一篇讲类加载器时说的类的模板。也就是类的属性、构造器、方法、常量池等。而且,方法区是一种规范,不是具体实现。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
来源: