JVM简单入门(双亲委派机制 GC机制及算法 JVM调优)

三大主流的JVM

  • SUN公司的Hotspot VM
  • BEA公司的JRockit
  • IBM公司的J9
    Hotspot有方法区,而JRockit和J9都没有方法区,我们大多数用的第一种
    在这里插入图片描述

JVM的主要组成部分

   JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

在这里插入图片描述

  • Class loader(类装载):根据给定的全限定名类名(如java.lang.Object)来装载class文件到Runtime data area中的方法区
  • Execution engine(执行引擎):执行classes中的指令。
  • Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
  • Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

JVM 运行时数据区

在这里插入图片描述

  • 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
  • Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
  • 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
  • Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
  • 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

Java程序运行机制

  • 编写Java源代码,源文件的后缀为.java;
  • 再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class;
  • 类加载器把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行
  • 命令解析器执行引擎(ExecutionEngine),将字节码翻译成底层系统指令
  • 交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
    在这里插入图片描述

虚拟机类加载机制

    Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类
  • 显式装载, 通过class.forname()等方法,显式加载需要的类
  • .隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

类加载器

在这里插入图片描述

  • 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;

  • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;

  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
    双亲委派机制

    如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,
    每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,
    只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
    

例如:

package java.lang;  //本地创建的String类

public class String {
    
    
    public  String toString(){
    
    
        return  "hello";
    }
    public static void main(String[] args) {
    
    
        String s = new String();
        s.toString();
    }
}

在这里插入图片描述
由于双亲委派机制,会一直往上寻找java.lang.String类,父加载器找到了,子加载器不会去加载

堆栈的区别

  • 物理地址:
    堆的物理地址分配对对象是不连续的。因此性能慢些。
    栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
  • 内存分别:
    堆因为是不连续的,所以分配的内存是在 运行期 确认的,因此大小不固定。一般堆大小远远大于栈。
    栈是连续的,所以分配的内存大小要在 编译期 就确认,大小是固定的。
  • 存放的内容:
    堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
    栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
  • 程序的可见度
    堆对于整个应用程序都是共享、可见的。
    栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
  • GC机制:针对的是堆
    栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就over

堆的分区

在这里插入图片描述

  • Eden区:新对象都是在此new出来的

  • 老年代区:经过GC机制未清理的对象(长期存活的对象或者新生代无法容纳的大对象)进入老年代

  • 元空间:逻辑上存在,物理上不存在这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,java运行时的一些环境或类信息,不存在垃圾回收,关闭JVM就会释放这个区域的内存

     元空间的发展历程:
     1.6之前 永久代   常量池是在方法区中
     1.7永久代   但慢慢退化了-有了去永久代的想法     常量池在堆中  
     1.8后   无永久代    常量池在元空间中
     老年代中的对象:
     年龄达到一定的程度(默认是15)的对象
     在 survivor 空间中相同年龄所有对象大小的总和>survivor空间的一半
    

在这里插入图片描述

GC垃圾回收机制

   当程序运行时,至少会有两个线程开启启动,一个是我们的主线程,一个时垃圾回收线程,垃圾回收线程的priority(优先级)较低。
垃圾回收器会对我们使用的对象进行监视,当一个对象长时间不使用时,垃圾回收器会在空闲的时(不定时)对对象进行回收,释放内存空间,程序员是不可以显示的调用垃圾回收器回收内存的,但是可以使用System.gc()方法建议垃圾回收器进行回收,但是垃圾回收器不一定会执行。

Java垃圾回收机制可以有效的防止内存溢出问题,但是它并不能完全保证不会出现内存溢出。
例如:

package com.heng;
import java.util.Random;

public class Text {
    
    
    public static void main(String[] args) {
    
    
        String s="liuheng";
        while (true){
    
    
           s+= s+new Random().nextInt(88888888)+new Random().nextInt(88888888);
        }
    }
}

调参获取GC细节 -Xms8m -Xmx8m -XX:+PrintGCDetails
在这里插入图片描述
在这里插入图片描述
内存满了,内存发生溢出:
在这里插入图片描述
如何解决这个问题:

  1. 尝试扩大堆内存看结果
  2. 分析内存,看一下那个地方出现了问题(MAT,JProfiler专业分析工具)

JProfiler插件安装

方式一:IDEA上下载Settings–>plugins->JProfiler

在这里插入图片描述

方式二:官网下载插件

https://plugins.jetbrains.com/plugin/253-jprofiler/versions
在这里插入图片描述
然后把从下载的压缩包解压出来的JProfiler文件夹,copy到IDEA自定义插件目录,默认路径:C:\Users\Administrator.IntelliJIdea2017.2\config\plugins
在这里插入图片描述
JProfiler监控软件安装
官方下载地址:https://www.ej-technologies.com/download/jprofiler/version_92
idea集成一下
在这里插入图片描述在这里插入图片描述

常用的 JVM 调优的参数

  • -Xms2g:初始化推大小为 2g;
  • -Xmx2g:堆最大内存为 2g;
    Xms 是设置初始化内存分配大小 默认是1/64
    Xmx 是设置最大分配内存 默认是1/4
  • -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
  • -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
  • -XX:+PrintGC:开启打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 详细信息。

GC算法

标记-清除算法

在这里插入图片描述

  1. 标记阶段:标记出可以回收的对象。
  2. 清除阶段:回收被标记的对象所占用的空间。
    优点: 实现简单,不需要对象进行移动。
    缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。

复制算法

  它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。

在这里插入图片描述
优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

标记-整理算法

标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,
在标记可回收的对象后将所有存活的对象压缩到内存的一端,使紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。

在这里插入图片描述
优点:解决了标记-清理算法存在的内存碎片问题。
缺点:仍需要进行局部对象移动,一定程度上降低了效率。
总结
在这里插入图片描述

分代垃圾回收器是工作流程

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  1. 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  2. 清空 Eden 和 From Survivor 分区;
  3. From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达15(默认配置是15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。谁空谁是to

JVM 有哪些垃圾回收器?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1
    新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

不同收集器之间的连线表示它们可以搭配使用。
在这里插入图片描述
面试题:详细介绍一下 CMS 垃圾回收器?

 CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
 CMS 使用的是标记-清除的算法实现的,所以在 gc的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

猜你喜欢

转载自blog.csdn.net/qq_45637894/article/details/122894825
今日推荐