前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM --- 堆&栈&堆参数调优

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

作者头像
贪挽懒月
发布2021-03-26 11:25:04
6040
发布2021-03-26 11:25:04
举报
文章被收录于专栏:JavaEE

一. 方法区:

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

代码语言:javascript
复制
Person p1 = new Person();
Person p2 = new Person();

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的默认大小,怎么证明呢?用下面的代码可以证明:

代码语言:javascript
复制
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的大小,以及打印堆的信息:

代码语言:javascript
复制
-Xms1024M -Xmx1024M -XX:+PrintGCDetails

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

执行结果

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

3. OOM异常:

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

代码语言:javascript
复制
String str = "hello";
while (true) {
    str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
}

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

代码语言:javascript
复制
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相关信息,如下:

代码语言:javascript
复制
[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信息是什么意思呢?拿出一条来分析一下:

代码语言:javascript
复制
[
  GC (Allocation Failure)
  [PSYoungGen: 1855K->491K(2560K)]
  1855K->991K(9728K), 0.0011226 secs
] 
[
  Times: user=0.00 sys=0.00, real=0.00 secs
] 

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

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

full GC的也是一样的:

代码语言:javascript
复制
[
  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] 

这里的意思分别是:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 方法区:
  • 二. 栈:
  • 三. 堆:
  • 四. JVM调优
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档