线程共享的运行时内存区域,它存储了每一个类的结构信息。什么叫类的结构信息,其实就是上一篇讲类加载器时说的类的模板。也就是类的属性、构造器、方法、常量池等。而且,方法区是一种规范,不是具体实现。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 = new Person();
Person p2 = new Person();
p1、p2是引用,上面说了,引用是栈中的,new Person()
是在堆中完成的,Person的模板是存在方法区的,也就是,堆中new对象的时候,用的是方法区中的模板,所以能保证new出来的两个Person实例结构都是一样的。所以栈中的p1、p2存储的是实例在堆中地址值。
1. 堆基本介绍:
一个JVM实例只存在一个堆,堆的内存大小可以调节,存放的是new出来的实例和数组。堆内存逻辑上分为三部分:
伊甸区 : S0区 : S1区 = 8 : 1 : 1
,而且,from区和to区不是固定的,谁空谁是to;永久区/元空间是逻辑上的划分,所以物理上堆内存就是新生区 + 养老区。
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异常。
1. 基本介绍:
JVM调优,其实就是堆参数的调整。
jvm调优
先说一下这张图,Minor GC就是上面说的YGC,Major GC就是full GC,S0和S1区有双向箭头,表示它们不是固定的,谁空谁就是to区(S1区)。
常见堆参数:
1/64
;1/4
;2. 堆内存调优简介:
上面说了xms和xmx的默认大小,怎么证明呢?用下面的代码可以证明:
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors()); // cpu核数
double xms = Runtime.getRuntime().totalMemory() / 1024 / 1024; // xms
double xmx = Runtime.getRuntime().maxMemory() / 1024 / 1024; // xmx
System.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 + new Random().nextInt(88888888) + new Random().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.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
这就是OOM异常了,并且中报错之前,打印了GC相关信息,如下:
[GC (Allocation Failure) [PSYoungGen: 1855K->491K(2560K)] 1855K->991K(9728K), 0.0024182 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2168K->272K(2560K)] 2668K->1753K(9728K), 0.0022543 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1621K->272K(2560K)] 7032K->5682K(9728K), 0.0009517 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 272K->272K(2560K)] 5682K->5682K(9728K), 0.0007530 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 272K->0K(2560K)] [ParOldGen: 5410K->3170K(7168K)] 5682K->3170K(9728K), [Metaspace: 2752K->2752K(1056768K)], 0.0076104 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 40K->32K(2560K)] 5829K->5821K(9728K), 0.0007024 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 32K->32K(1536K)] 5821K->5821K(8704K), 0.0005743 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 5789K->4479K(7168K)] 5821K->4479K(8704K), [Metaspace: 2752K->2752K(1056768K)], 0.0062029 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 20K->32K(2048K)] 7118K->7130K(9216K), 0.0007837 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 7098K->3170K(7168K)] 7130K->3170K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0054553 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 20K->0K(2048K)] 5809K->5789K(9216K), 0.0003817 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5789K->5789K(9216K), 0.0003808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5789K->5789K(7168K)] 5789K->5789K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0040374 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5789K->5789K(9216K), 0.0003072 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5789K->5775K(7168K)] 5789K->5775K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0075411 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
可以看到,这里先进行GC,然后是full GC,full GC也搞不定了,就报错了,这与上面讲的是一样的。那么打印的GC信息是什么意思呢?拿出一条来分析一下:
[
GC (Allocation Failure)
[PSYoungGen: 1855K->491K(2560K)]
1855K->991K(9728K), 0.0011226 secs
]
[
Times: user=0.00 sys=0.00, real=0.00 secs
]
我们一行一行的看,意思分别是:
表示由于分配失败发生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.0075411 secs
]
[Times: user=0.01 sys=0.00, real=0.01 secs]
这里的意思分别是: