初识JVM

1、JVM架构图

 

 

 2、类加载器

  1、启动类加载器(根类加载器Bootstrap Class Loader)

    用来加载Java的核心类库(jre/lib/rt.jar)

  2、扩展类加载器(Extension Class Loader

    用来加载Java/lib/ext

  3、系统类加载器(应用程序加载器 System Class Loader)

    加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径

  4、自定义类加载器(略)

3、双亲委派机制

  简单的说就是一个类加载器在接到加载类请求时,会向上委托给自己的父类加载器加载一直到启动类加载器;如果父类可以完成加载任务,就成功返回;父类无法加载才会自己去加载;如果都无法完成加载时就会抛出ClassNotFound的错误;

4、Native关键字  -  (修饰的方法都存在本地方法区)

  凡是native修饰的,说明Java的作用范围达不到了,会去调用底层C/C++的库;

  调用本地库接口(JNI)加载本地方法库中的方法;

    JNI作用:扩展Java的使用,融合不同的编程语言为java所用;

5、程序寄存器

  每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也就是即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计;

6、方法区

  所有线程共享,静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法中,但是实例变量存在堆内存中,和方法区无关;简单说,所有定义的方法的信息都保存在该区域,此区域属于共享空间;

  static修饰、final修饰、Class、常量池;

7、栈

  栈内存,主管程序的运行,生命周期和线程同步;线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题;会存在栈溢出问题(StackOverFlowError),如递归调用。。。

8、堆

  这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组,堆内存的大小是可以调节的;

  堆内存中细分为三个区域:

      

 

 

 

    

 

 

     所有对象时在伊甸园区创建的;

#测试机8G内存
//
JVM试图使用的最大内存 long l = Runtime.getRuntime().maxMemory(); // JVM的总内存 long l1 = Runtime.getRuntime().totalMemory(); System.out.println("最大内存:"+l/(double)1024/1024);//1796M System.out.println("JVM的总内存:"+l1/(double)1024/1024);//240M

  默认情况下:分配的总内存是电脑内存的1/4,而初始化的内存是1/64;

  扩展1:遇到OOM(Out Of Memory)该怎么处理?

      1、可以尝试调大VM分配内存:

#VM OPTIONS命令
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
    -Xms:设置初始总内存分配大小,默认1/64
    -Xmx:设置最大分配内存,默认1/4
    XX:+PrintGCDetails:打印GC信息

      控制台查看配置后的结果:1.8之后,元空间(之前叫永久代)逻辑上存在,物理上不存在,由下图可以看出; 

 

 

       2、调大内存后还是出错,就分析内存快照,看一下哪个地方出现了问题(需要借助专业工具)

        测试工具有JProfiler、MAT等,这边用JProfiler分析Dump内存文件;

        首先,本机安装JProfiler;然后IDEA也要安装扩展,IDEA安装完重启下工具栏就会显示图标了;

 

 

         然后,本机安装好后,IDEA还要配置下安装路径;

  也是需要配置下VM参数;

#VM OPTIONS 为了方便测试,将虚拟机内存调小了,然后指定Dump内存溢出错误时文件
-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError

  运行之后会在项目根目录生成一个.hprof文件,双加开发即可:

 

 

9、GC分代回收算法

  参考链接:很详细清晰;

  GC的作用区域在堆和方法区;

  JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代:新生代 -》幸存区(分from,to两块,动态变换,空的变为to区)-》老年代

  默认配置当一个对象经历了15次GC,还没有死,就可以进入老年代了;以下参数可以调整进入老年代的次数

#VM OPTIONS
-XX:+MaxTenuringThreshold=16

  1、两种GC

    轻GC(普通的GC):

      1)在不断创建对象的过程中,当Eden区域被占满,此时会开始做轻GC;

      2)此时不能回收的被放入幸存To区

      3)如果幸存From区也是空的,那么From区变为To区,To区变为From区;如果幸存From区已存放有对象就复制到To区,此时From区清空再互换(From、To)身份(From和To是逻辑层次的,反正就是幸存区总会有一个是空的状态,称之为To区);

      4)依次类推,始终保证幸存区有一个空的,用来存储临时对象,用于交换空间的目的。反反复复多次没有被淘汰的对象,将会被放入Old区域中,默认15次(由参数--XX:MaxTenuringThreshold=15 决定);

      

    重GC(全局GC/Full GC):

      JVM会安全的暂停所有正在执行的线程,来回收内存空间,在这个时间内,所有除了回收垃圾的线程外,其他有关JAVA的程序,代码都会静止,反映到系统上,就会出现系统响应大幅度变慢,卡机等状态。

      触发Full GC的情况:

        1)System.gc()方法的调用:

此方法的调用是建议JVM进行Full GC,尽管仅仅是建议而非一定,但非常多情况下它会触发 Full GC,从而添加Full GC的频率,也即添加了间歇性停顿的次数。
强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+DisableExplicitGC来禁止RMI调用System.gc。

        2)老年代空间不足

一种是分配一个对象,空间真的不足。另一种是由于内存碎片,导致没有连续内存空间来分配给对象。

        3)metaspace空间不足也会造成Full GC

metaspace中存放的为一些class的信息、常量、静态变量等数据,当系统中要载入的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满。在未配置为採用CMS GC的情况下也会执行Full GC。

        4)之前历次minorGC晋升到老年代的对象平均大小如果大于此时老年代的剩余空间,也会导致一次Full GC。

  2、常用算法

    引用计数器:每有一个引用就+1,基本不用;

    复制算法:

      优点:没有内存的碎片;

      缺点:浪费了空间,幸存区永远有一个是空的;假设百分百存活的话,复制起来效率很差;

    标记清除算法:

      优点:不需要额外的空间;

      缺点:两次扫描(标记,清除),严重浪费时间,会产生内存碎片;

    标记压缩:在标记清除两次扫描,再一次扫描,整理内存空间

      优点:防止内存碎片产生;

      缺点:又多一次扫描;

    总结:

内存效率(时间复杂度) 复制算法 > 标记清除算法 > 标记压缩算法
内存整齐度 复制算法 == 标记压缩算符 > 标记清除算法
内存利用率 标记压缩算符 == 标记清除算法 > 复制算法
年轻代 存活率低,用复制算法
老年代 区域大,存活率高,标记清除算法 + 标记压缩(混合);调优:设定几次标记清除后再压缩

 

    

  GC中相关问题

    问题1:怎么定义活着的对象?

      从根引用开始,对象的内部属性可能也是引用,只要能级联到的都被认为是活着的对象。

    问题2:什么是根?

      本地变量引用,操作数栈引用,PC寄存器,本地方法栈引用等这些都是根。

    问题3:对象进入Old区域有什么坏处?

      Old区域一般称为老年代,老年代与新生代不一样。新生代,我们可以认为存活下来的对象很少,而老年代则相反,存活下来的对象很多,所以JVM的堆内存,才是我们通常关注的主战场,因为这里面活着的对象非常多,所以发生一次FULL GC,来找出来所有存活的对象是非常耗时的,因此,我们应该避免FULL GC的发生。

    问题4:S0和S1一般多大,靠什么参数来控制,有什么变化?

      一般来说很小,我们大概知道它与Young差不多相差一倍的比例,设置的参数主要有两个:

-XX:SurvivorRatio=8
-XX:InitialSurvivorRatio=8

      第一个参数(-XX:SurvivorRatio)是Eden和Survivous区域比重(注意Survivous一般包含两个区域S0和S1,这里是一个Survivous的大小)。如果将-XX:SurvivorRatio=8设置为8,则说明Eden区域是一个Survivous区的8倍,换句话说S0或S1空间是整个Young空间的1/10,剩余的8/10由Eden区域来使用。

      第二个参数(-XX:InitialSurvivorRatio)是Young/S0的比值,当其设置为8时,表示S0或S1占整个Young空间的1/8(或12.5%)。

    问题5:一个对象每次Minor GC时,活着的对象都会在S0和S1区域转移,讲过MInor GC多少次后,会进入Old区域呢?

      默认是15次,参数设置

--XX:MaxTenuringThreshold=15

    ,计数器会在对象的头部记录它的交换次数

posted @ 2020-03-24 23:17  门虫不是虫  阅读(184)  评论(0编辑  收藏  举报