zoukankan      html  css  js  c++  java
  • java:JVM及相关概念

    1、概述

    我们实际的开发中,先利用JDK(调用本地的API)开发属于自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解释这些字节码,映射到CPU指令集或OS的系统调用。所以在讲JVM之前,我们先来讲一下容易和JVM搞混的两个概念JDK和JRE。简单粗暴的理解就是:JDK包含JRE,JRE包含JVM

    JVM:Java虚拟机,它只认识xxx.class这种类型的文件,它能够将class文件中的字节指令进行识别,并调用操作系统上API完成动作。所以说,jvm是Java能够跨平台的核心。

    JRE:Java运行时环境,jre包括jvm和java核心类库与支持文件。

    JDK:Java开发工具包,jdk是整个java开发的核心,包括了java运行环境(jre)、一堆java工具(javac、java、jdb等)和java基础类库(即Java API包括rt.jar)。

    2、JVM基本概念

    2.1 什么是JVM

    jvm中文名称叫做Java虚拟机,它是由软件技术模拟出来计算机运行的一个虚拟的计算机。

    jvm也充当这个一个翻译官的角色,我们编写出java程序后,是不能直接被操作系统所识别的,这时候jvm的作用就体现出来了,它负责把我们的程序翻译给操作系统听,告诉它我们的系统需要做什么操作。

    我们都知道Java的程序需要经过编译后,产生.Class文件,JVM才能识别并运行它,JVM针对每个操作系统开发其对应的解释器,所以只要其操作系统有对应版本的JVM,那么这份Java编译后的代码就能够运行起来,这就是Java能一次编译,到处运行的原因。

    2.2、JVM基本原理介绍

    jvm体系总体分四大块:

    • 类的加载机制
    • jvm内存结构
    • GC算法 垃圾回收
    • GC分析 命令调优

     3、JVM运行时数据区

     运行的程序是内容是放在运行时数据区中的,如上图蓝色那块依次来说明一下:

    3.1 堆

    保存所有引用类型的真是信息(线程共享),也是说那些new出来的对象都是放在这块区域的。

    3.2 虚拟机栈

    虚拟机栈:线程私有,生命周期和线程一致。主要用来存储方法运行过程中一些临时变量,是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。当一个方法运行到一半需要调用另一个方法时,就创建一个新的栈帧表示新调用的方法,将原来那个方法压入栈中。当方法运行完毕,栈帧出栈,原来方法处于栈顶接着运行。和栈这一数据结构一样,虚拟机栈里面的栈帧遵循后进先出的原则。

    局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址) 

    3.3 方法区

    又叫静态区,跟堆一样,被所有的线程共享。在jdk7之前被称为永久带,在jdk8之后改为了元数据空间。它主要是存储一些static方法或者变量,类加载器classLoader等一些全局的数据。同时方法区里面还有一个叫常量池的地方,String的字符串等常量存储就存储在那边。

    3.4 程序计数器

    一个非常小的内存空间,用来保存程序执行到的位置(线程私有)。下面是一个程序计数器的演示:
    public class TestDemo{
        public static void main(String args[]){
            String str = null;
            str.length();
        }
    }
    

    上面程序会报空指针异常,如下图,在报的这个异常中,有一行日志 at TestDemo,main(TestDemo.java:4) 代表程序运行到TestDemo 中main()函数第四行的时候发生的错误,就是通过程序计数器来记录这个程序运行的位置的。

     3.5 本地方法栈

    和虚拟机栈类似,不过本地方法栈里面运行的方法不是用java写的,一般是用c或c++写的为虚拟机使用到Native方法服务,也有类似栈帧的的概念。

     用一张图描绘每个区域存储的内容

    4、内存模型和垃圾回收

    正式阅读之前需要了解相关概念:Java 堆内存分为新生代和老年代,新生代中又分为1个 Eden 区域 和 2个 Survivor 区域。

     4.1 概述

    jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。

     4.2 GC的对象

    需要进行回收的对象就是已经没有存活的对象,判断一个对象是否存活有两种方法:引用计数和可达性分析

    引用计数:每一个对象都有一个引用计数属性,新增一个引用时计数增加1,释放引用时计数减1。计数为0时可以回收。此方法简单,但无法解决对象互相引用的问题。

     从上图中可以看出,如果不小心把Obj1-reference 和 Obj2-reference置null。则在java堆中的两块内存依然保持互相引用无法回收。

    可达性分析: 从GC Root开始向下搜索,搜索所走过的路径称之为引用链。当一个对象到GC Root没有任何引用链相连时,则证明此对象不可用,不可达对象。

     

     可作为 GC Roots 的对象:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象

    4.3 什么时候触发GC

    1.程序调用System.gc时可触发

    2.系统自身决定触发GC的时机(根据Eden和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)

    GC又分为Minor gc和Full gc

    Minor gc:当Eden区满是触发,Minor gc

    Full gc:

      a.调用System.gc时,系统建议执行Full GC,但是不必然执行

      b.老年代空间不足

      c.方法区空间不足

      d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存

      e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

    GC主要做了清理对象,整理内存的工作。Java堆分为新生代和老年代,采用了不同的回收方式。(回收方式即回收算法详见后文)

    4.3.1 回收方法区:

    在堆中,尤其是在年轻代中,一次垃圾回收一般可以回收 70% ~ 95% 的空间,而永久代的垃圾收集效率远低于此。永久代垃圾回收主要两部分内容:废弃的常量和无用的类。

    判断废弃常量:一般是判断没有该常量的引用。

    判断无用的类:要以下三个条件都满足

    • 该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例
    • 加载该类的 ClassLoader 已经被回收
    • 该类对应的 java.lang.Class 对象没有任何地方呗引用,无法在任何地方通过反射访问该类的方法

    4.4 GC算法

    GC常用算法有:标记-清除算法,标记-压缩算法,复制算法,分代收集算法。

     目前主流的JVM(HotSpot)采用的是分代收集算法。

    分代收集算法

    现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代(Young)和老年代(Tenure)。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。
    具体过程:新生代(Young)分为Eden区,From区与To区

     当系统创建一个对象的时候,总是在Eden区操作,当这个区满了,那么就会触发一次YoungGC,也就是年轻代的垃圾回收。一般来说这时候不是所有的对象都没用了,所以就会把还能用的对象复制到From区。 

     这样整个Eden区就被清理干净了,可以继续创建新的对象,当Eden区再次被用完,就再触发一次YoungGC,然后呢,注意,这个时候跟刚才稍稍有点区别。这次触发YoungGC后,会将Eden区与From区还在被使用的对象复制到To区, 

     

     再下一次YoungGC的时候,则是将Eden区与To区中的还在被使用的对象复制到From区

    经过若干次YoungGC后,有些对象在From与To之间来回游荡,这时候From区与To区亮出了底线(阈值),这些家伙要是到现在还没挂掉,对不起,一起滚到(复制)老年代吧。 

     

     老年代经过这么几次折腾,也就扛不住了(空间被用完),好,那就来次集体大扫除(Full GC),也就是全量回收。如果Full GC使用太频繁的话,无疑会对系统性能产生很大的影响。所以要合理设置年轻代与老年代的大小,尽量减少Full GC的操作。

    参考:

    https://blog.csdn.net/csdn_aiyang/article/details/72876272

    https://blog.csdn.net/laomo_bible/article/details/83112622

    https://blog.csdn.net/qq_41701956/article/details/81664921

     


  • 相关阅读:
    Android 获取View在屏幕中的位置【转】
    算法学习资源 -- 2018年8月21日星期二
    Activity SingleInstance启动模式
    Android Studio添加aar依赖的两种方式
    Multiple dex files define Lcom/google/gson/internal/Streams$AppendableWriter$CurrentWrite;
    Android Studio添加aar依赖
    jdk-8u181-docs.chm -- 制作时间2018年8月12日
    大串中查找校串出现的次数(11)
    字符串反转(10)
    String类的替换方法(9)
  • 原文地址:https://www.cnblogs.com/mengY/p/12202486.html
Copyright © 2011-2022 走看看