JVM内存模型-回忆学习总结

jvm在我们日常开发过程中比较少使用,但是当我们考虑到架构设计以及系统层面流程开发时,就不得不考虑jvm内存模型,JVM规定java运行的内存申请、分配、管理的策略,特别是在性能调优,系统异常排查是经常使用,所以要懂JVM才能搭建高效稳定的系统

参考经典JVM内存模型分五块:

1、Method Area:

主要存放类加载信息(类的生命周期:加载、校验、准备、解析、初始化、使用、卸载)、常量、静态变量以及JIT编译后的class等数据,方法区的特点有几个:全局共享、永久(伴随JVM生命周期,一般不会发生gc),此处不允许垃圾回收机制,也不允许扩展,当方法区内存不够时会发生OutOfMemoryError,个人没怎么使用调节方法去内存

常见的方法区设置大小,目前常用JDK8以上元空间,如下设置常量池大小,但是不常用,没有实战经验,

-XX:MetaSpaceSize和-XX:MaxMetaspaceSize=5M

jdk7之前用如下设置大小

-XX:PermSize和-XX:MaxPermSize

 

2、Heap

堆内存分配和系统的运行效率息息相关,也是OOM的主要来源,此内存存放类的实例对象,一般堆内存是可扩展,常用设置堆的大小如下

-Xms    -Xmx

堆又分为几个区新生代eden和老年代(jdk7之前是老年代),它两占用堆空间比例使用 –XX:NewRatio=n指定大小,此外常用线上生成堆快照指令如下:

jmap -dump:live,format=b,file=heap-dump.bin <pid>

新生代

  新生代又分为Eden、from survivor(S0)、to survivor(S1),它们之间的比例一般是8:1:1,我们常用的几种gc时期minor gc就发生在Eden,young gc 发生在整个新生代的Eden、s0或者s1.整个区的大小指定是-Xmn指定大小,此处一般常调整大小

老年代

新建的对象一般优先进去eden,新生代一般会发生minor gc新建的大对象无法在新生代存放时直接过度老年代,majoy gc就发生在老年代

对此可能有人疑问,full gc又是如何发生的呢?不用想的话当然是老年代发生full gc,当新生代对象没有被gc root关联时,经过多次垃圾回收没有被回收存放到老年代时,由于老年代发生major gc也无法回收,堆内存无法存储将会导致full gc,以便腾出空间。发生full gc是非常严重的问题,直接导致应用变慢甚至应用停顿。

新生代默认发生15次 young gc,14次之后若不被回收将会进入老年代,新生代和老年代以及发生OOM一般流程如下,我们常设置新生代和老年代占用比例,其实是设置young gc的频率来达到一定效果。

heap调优有如下参数,一般不可能完美流程,根据CAP原则(一致性consistency、可用性availability和分区容错性partition tolerance),只能根据应用自身情况设置

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof 设置jvm堆异常快照的地址
-Xms  -Xmx 设置堆最大值和最小值
-XX:MaxPermSize=n 持久代大小
–XX:NewRatio=n 设置新生代和老年代的占用比例
-XX:SurvivorRatio eden和survivor比例
-XX:MaxTenuringThreshold young gc次数,默认15
   
-XX:+UseParallelGC 并行收集器
-XX:+UseSerialGC 串行收集器
-XX:+UseG1GC 使用G1算法
-XX:MaxGCPauseMillis G1最大停顿时间,毫秒
   
-XX:+PrintGC 打印gc日志
-XX:+PrintGCDetails 垃圾回收数据详情
-XX:+PrintGCTimeStamps 回收时间,可以上并存
-Xloggc:filepath 记录gc详细日志文件

常用的jvm堆调试工具有:

arthas 阿里巴巴集成工具

top 命令查找java进程

jconsole远程或者本地调试

jvisualvm 可视化监控

jstack -l pid >> filepath  栈信息

jmap -dump:live,format=b,file=heap-dump.bin <pid>  生成快照

jhat 分析map信息

3、虚拟机栈VM Stack

栈的概念大家应该就会想到入栈和出栈的概念,其实在方法调用过程就是栈帧的入栈和出栈,虚拟机栈的主要内容是局部信息,线程私有的,比如局部变量、操作栈、动态连接、方法返回地址之类的。此处常见就是栈的深度超出限定即StackOverflowError,比如我们看如下代码:

package com.jvm;

public class MainTest {
    public static void main(String[] args) throws InterruptedException {
        try {
            addStackLength();
        } catch (Throwable e) {
            System.out.println("stackLength=" + stackLength);
            e.printStackTrace();
        }
        try {
            stackLength = 0;
            addStackLength();
        } catch (Throwable e) {
            System.out.println("stackLength=" + stackLength);
            //e.printStackTrace();
        }
    }

    private static int stackLength = 0;

    public static void addStackLength() {
        stackLength++;
        MainTest.addStackLength();
    }
}

上面代码将会输出

stackLength=23366
java.lang.StackOverflowError

stackLength=62790

java.lang.StackOverflowError

会发现诡异问题,上面每次运行栈的深度是不固定的,这个和JIT有关,详细请查看JIT

用javap -verbose filepath上面代码方法如下

public static void addStackLength();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0    // 栈最大深度2 ,局部变量0 ,入参0
         0: getstatic     #9                  // Field stackLength:I
         3: iconst_1                 // 常量值入栈1
         4: iadd                    // ++
         5: putstatic     #9                  // Field stackLength:I 设置变量值
         8: invokestatic  #2                  // Method addStackLength:()V 调用静态方法V
        11: return
      LineNumberTable:
        line 23: 0
        line 24: 8
        line 25: 11

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #9                  // Field stackLength:I
         4: return
      LineNumberTable:
        line 20: 0
}

从中可以看出每个方法调用时都是虚拟机栈的入栈和出栈过程。

4、本地方法栈

本地方法栈主要和Native方法服务有关,这里不受JVM的约束,可以自主阅读相关材料!

5、程序计数器

程序计数器主要和CPU打交道一样,记录虚拟机的执行指令的偏移量和行号指令器等,这里没有内存溢出的概念!

总结

虚拟机使用场景不对,但是很有价值和意义,个人在搭建一次某银行中小型系统过程中使用比较多的是堆内存优化还有排查的简单工具使用。还有个人编写能力也不是很好,如有不足之处多多指教!

猜你喜欢

转载自blog.csdn.net/soft_z1302/article/details/110181772
今日推荐