6:JVM内存模型深度剖析与优化(JVM可视化工具 与 与 共

2021-04-10  本文已影响0人  _River_
1:JVM可视化工具:(JavaVisualVm)
注意:了解安装完该工具后 先看后面的  方法区(元空间)(共享)  与 堆 (共享)

jvisualvm(JDK自带调试工具  JavaVisualVm)
会实时监测JVM中启动的线程  (可以启动一个死循环的线程进行测试)
Visual GC(可视化垃圾回收插件)是需要安装的
可以在JavaVisualVm的 工具栏->工具->插件—>可用插件 安装Visual GC
可能会更新失败,可以采用网上教程解决,或者使用离线安装;

如果需要离线安装:
资源跳转链接:
https://visualvm.github.io/pluginscenters.html
1:把下载下来的 Visual GC插件 通过添加插件的方式添加
2:然后进行安装
3:然后就可以通过 Visual GC 对某个线程进行 可视化垃圾回收监控
离线安装教程:https://blog.csdn.net/qq_39175358/article/details/104708519
2:方法区(元空间)(共享) 详解运行时数据区(内存模型):
使用 javap -v Math.class 查看(反编译 加附带信息)

常量池
    1:常量
    2:静态变量 (加了static 关键字的 User)
    3:类信息 (C++ 类的方法名称等等)

方法区里面的user放的是  user在堆里面的地址  
类比于 main线程 main方法栈帧的局部变量表的math  存放的是math这个对象在堆里面的地址

注意:
方法区(元空间) 使用的是直接内存(也就是物理机内存)
方法区(元空间)以前叫做永生代
方法区(元空间) 也会引发full gc 后续会提及
3:堆 (共享) 详解运行时数据区(内存模型):
存放:
    如 new出来的Math的  math对象 (非地址  是他的内容)
    如 静态变量 (加了static 关键字的 User)的 user对象(非地址 是他的内容)

注意:类加载器 ClassLoader 的对象也是放在堆里面 实际上就是一个对象 

堆实际上是什么东西:
默认 老年代:占 三方之二
默认 年轻代:占 三分之一  其中 Eden占 80%   s0占10%    s1占10%

gc过程(先了解):GCRoot  (引用对象 非垃圾对象)
什么是GCRoot  如:方法区中的 静态变量 的 user    如:栈(线程)的 局部变量表中的 math 。。。

先粗略了解:
1:Web应用 new 出来的对象 绝大部分都是先扔年轻代的 Eden

2:如果年轻代的Eden满了   触发步骤3
3:字节码执行引擎  开启一个垃圾回收线程 进行minor gc   (回收Eden 和 s0 s1)
        1:把GCRoot的非垃圾对象   user math  
             复制到Survivor区s0  对象的头:年龄+1
        2:把Eden 的全部对象全部回收 (包括user math )
        
4:如果年轻代的Eden又满了   触发步骤5
5:字节码执行引擎  开启一个垃圾回收线程 进行minor gc  (回收Eden 和 s0 )
        1:把GCRoot的非垃圾对象 复制到  Survivor区s1  
        2:如果Survivor区s0的对象  user  math 不被回收  复制到  Survivor区s1  同时  user  math 对象的头:年龄+1
             
        3:把Eden 的全部对象全部回收 (包括user math )
            把Survivor区s0 的全部对象回收 (包括user math )

6:如果年轻代的Eden又满了   触发步骤7
7:字节码执行引擎  开启一个垃圾回收线程 进行minor gc   (回收Eden 和 s1)
        1:把GCRoot的非垃圾对象 复制到  Survivor区s0 
        2:如果Survivor区s1对象  user  math 不被回收  复制到  Survivor区s0 同时  user  math 对象的头:年龄+1
        
        3:把Eden 的全部对象全部回收 (包括user math )
            把Survivor区s1的全部对象回收 (包括user math )
        
8:当年龄变成15 (默认)时 会被扔到老年代    (里面静态变量比较多)   
4:GC 可视化简单实战
public class HeapTest {

    public static void main(String[] args) throws InterruptedException {
        ArrayList<Heap> heapArrayList = new ArrayList<>();
        while (true){
          //不断的new 一个对象
            heapArrayList.add(new Heap());
            Thread.sleep(100);
        }
    }

}

public class Heap {
    //一个对象里面起码占 100kb内存
    byte [] bytes = new byte[1024*100];

}
1:注意时间  Compile Time 和 Class Loader Time 
2:注意 GC Time (垃圾回收的时间)
3:注意Eden Space 与 Survivor 0  Survivor 1 的关系
4:注意 Old Gen  缓慢而持续的上升 (当Old Gen满的时候内存溢出 查看代码报错)
5:注意 MetaSpace 方法区保持不动

图1
    一开始的 GC 回收次数 为0
    Eden在上升
    s0 与 s1 为空
    Enen满后  进入图2

图2:
    此时的 GC 回收次数 为1
    s1 获取 Eden 复制过来的 非垃圾回收对象
    Eden清空后重新上升                 
图3:
    此时的 GC 回收次数 为2
    s0 获取 Eden 复制过来的对象
    s0 获取 s1存在的 非垃圾回收对象
    s1 清空
    Eden清空后重新上升

图2 图3 操作不断重复 
当:   在Eden到S0或S1   S0->S1 或者 S1->S0 时计转移次数加1  到达计数15时进 Old Gen
导致Old Gen  缓慢而持续的上升

图四:    
    当Old Gen   第一次需要进行回收的空间满了: 会触发 full gc 回收整个堆 以及 方法区(元空间) 的垃圾回收对象
    但最后full gc都无法回收的时候
    如果Old Gen 满了之后会出现异常:OOM 内存溢出

gc 不管 minor gc(时间短)full gc(时间长) 都会出现 stop the world(STW);
stop the world(STW):停止掉用户发起的所有线程  (比如下单),避免在收集垃圾的过程中产生新的垃圾

java虚拟机调优就是减少 stop the world(STW)的过程    

5:JVM内存分配设置
Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):

java 
‐Xms2048M ‐Xmx2048M ‐Xmn1024M 
‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M 
‐Xss512K
‐jar microservice‐eureka‐server.jar

方法区(元空间):
     推荐设置 JVM 的元空间大小(不然可能导致大一点的程序启动特别慢):

关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 
    指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M,
    达到该值就会触发full gc进行类型卸载, 容量分配大小的扩展机制 会对该值进行调整:
    容量分配大小的扩展机制:
        如果释放了大量的空间, 就适当降低该值; 
        如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。

这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。
方法区(元空间): 以前叫永久代使用物理机内存

栈(线程):
    ‐Xss512K  是指每给线程 512k
-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多
在不调节 -Xss的时候 默认是1m 可以调用23538次 redo方法  产生23538个redo栈帧
在调节    -Xss为128k的时候     可调用1088次 redo方法 产生1088个redo栈帧

线程结束:栈(线程)内存马上释放

特别注意:我们是没有办法设置系统能开多少个线程的
public class ThreadStackOverflowTest {
    //JVM设置
    // - Xss 128k  -Xss 默认 1M
    static  int count = 0;
    /**
     * 每一次 调用redo的时候
     * main线程 都新增一个 redo 的栈帧
     */
    static  void redo(){
        count ++;
        redo();
    }

    public static void main(String[] args) {
        try{
            redo();
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(count);
        }
    }
}

项目连接

请配合项目代码食用效果更佳:
项目地址:
https://github.com/hesuijin/hesuijin-study-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/hesuijin-study-project.git

jvm-module项目模块下  jvmMemoryModel包
上一篇 下一篇

猜你喜欢

热点阅读