JVM(Java虚拟机)(由生到熟)

文章目录

    • JVM概念
    • jvm位置
    • JVM框架:
      • 程序计数器(线程私有)
      • 虚拟机栈(线程私有)
      • 本地方法栈(Native Method Stack)(线程私有)
      • 方法区(Method Area)
      • 运行时常量池(方法区的一部分)
      • Java堆(线程共享)
    • 双亲委派机制:
    • 沙箱机制:
    • 如何判断对象已”死”
      • 引用计数法
      • 可达性分析算法
    • GC垃圾回收:
      • 复制算法
      • 标记清除法
      • 标记(压缩)清除算法
    • Minor GC和Full GC一样吗
    • JMM
    • 回收方法区(重点)
    • 垃圾收集器
      • CMS收集器(老年代收集器,并发GC)

JVM概念

JVM(Java Virtual Machine的简称。意为Java虚拟机。):
指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。 常见的虚拟机有JVM、VMwave、Virtual Box。.JVM和其他两个虚拟机的区别:
1、VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器
2、JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进行了裁剪JVM是一台被定制过的现实当中不存在的计算机

jvm位置

JVM就是一个软件,软件是运行在操作系统之上的,所以JVM的位置就是操作系统之上。

JVM框架:

JVM(Java虚拟机)(由生到熟)
线程私有区域:程序计数器、Java虚拟机栈、本地方法栈
线程共享区域:Java堆、方法区、运行时常量池

对个部分的的解释

程序计数器(线程私有)

是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器,就是一个指针,指向下一个要运行指令的地址。如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为空。

虚拟机栈(线程私有)

虚拟机栈描述的是Java方法执行的内存模型 : 每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口(方法返回地址)等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。声明周期与线程相同。
之前我们一直讲的栈区域实际上就是此处的虚拟机栈,再详细一点,是虚拟机栈中的局部变量表部分(存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用等)。

本地方法栈(Native Method Stack)(线程私有)

本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的Java方法服务
在HotSpot虚拟机中,本地方法栈与虚拟机栈是同一块内存区域

方法区(Method Area)

在JDK8以前的HotSpot虚拟机中,方法区也被称为”永久代”(JDK8已经被元空间取代)。方法区是被所有线程共享,存放的主要为以下的内容。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关(static ,final,Class,常量池)

运行时常量池(方法区的一部分)

运行时常量池是方法区的一部分,存放字面量与符号引用。
字面量 : 字符串(JDK1.7后移动到堆中) 、?nal常量、基本数据类型的值。
符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。

Java堆(线程共享)

Java堆(Java Heap)是JVM所管理的最大内存区域。Java堆是所有线程共享的一块区域,在JVM启动时创建。此内存区域存放的都是对象实例。
Java堆是垃圾回收器管理的主要区域,因此很多时候可以称之为”GC堆”。根据JVM规范规定的内容,Java堆可以处于物理上不连续的内存空间中。Java堆在主流的虚拟机中都是可扩展的(-Xmx设置最大值,-Xms设置最小值)。
如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM,堆又可分为以下几部分:

JVM(Java虚拟机)(由生到熟)

一般新创建的对象都在伊甸园区,(大对象直接放在老年代,因为大对象的创建和销毁是比较耗资源的,如果放在新生代,可能要不断的进行创建与销毁导致效率低下)当伊甸园区满了就会触发一次轻GC,活下来的幸存者会放入幸存者区(0、1区也被叫做from、to区),当幸存者区和伊甸园区都满了后,会触发一次重Gc,再活下来的对象会放到(默认GC15次)养老区。
永久区
这个区域常驻内存的。用来存放IDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存

jdk1.6之前︰永久代,常量池是在方法区;
jdk1.7 :永久代,但是慢慢的退化了,去永久代,常量池在堆中.
jdk1.8之后:无永久代,常量池在元空间

JVM类加载过程:

JVM(Java虚拟机)(由生到熟)
破坏双亲委派模型:
第一次:JDK 1.2引入双亲委派模型,为了兼容老代码出现了第一次破坏双亲委派。
第二次:它是双亲委派模型自身的缺点所导致第二次破坏,比如当父类出现了调用子类的方法的时候。
第三次:人们对于热更新和热加载的追求,导致了第三次双亲委派模型破坏。

沙箱机制:

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,系统资源包括CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。所有的Java程序运行都可以指定沙箱,可以定制安全策略。在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信住的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱(Sandbox)机制。组成沙箱的基本组件:
字节码校验器(bytecode verifier)、类装载器(class loader)、存取控制器(access controller)、安全管理器(security manager)、安全软件包(security package)

如何判断对象已”死”

引用计数法

引用计数描述的算法为:给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已”死”。
引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个不错的算法。比如Python语言就采用引用计数法进行内存管理。但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题

可达性分析算法

在上面我们讲了,Java并不采用引用计数法来判断对象是否已”死”,而采用”可达性分析”来判断对象是否存活。此算法的核心思想为 : 通过一系列称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为”引用链”,当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的(可以说就是不和GC Roots在同一个绳上的都是死亡对象,无法存活的)。
在Java语言中,可作为GC Roots的对象包含下面四种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

在JDK1.2之后,Java对引用的概念做了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减。

  • 强引用 : 强引用指的是在程序代码之中普遍存在的,类似于”Object obj = new Object()”这类的引用,只
    要强引用还存在,垃圾回收器永远不会回收掉被引用的对象实例。
  • 软引用 : 软引用是用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生
    内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才
    会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。
  • 弱引用 : 弱引用也是用来描述非必需对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存
    到下一次垃圾回收发生之前。当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱
    引用关联的对象。在JDK1.2之后提供了WeakReference类来实现弱引用。
  • 虚引用 : 虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存
    在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的
    唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用。

GC垃圾回收:

复制算法

:”复制”算法是为了解决”标记-清理”的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。(谁空谁是 to)将存活下来的对象放入to区,在将from区移到to区,然后from与to区交换,to区始终为空。
此算法实现简单,运行高效,无内存碎片,但内存浪费多。

JVM(Java虚拟机)(由生到熟)

标记清除法

第一次扫描,标记存活的对象,第二次扫描,清除未被标记的对象。
优点:没有额外的内存开销
缺点:两次扫描,浪费时间,会产生内存碎片。

标记(压缩)清除算法

针对老年代的特点,提出了一种称之为”标记-整理算法”。标记过程仍与”标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
对以上三种方法的总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
没有最好的算法,只有最合适的算法—–> GC∶分代收集算法
当前JVM垃圾收集都采用的是”分代收集(Generational Collection)”算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用”标记-清理”或者”标记-整理”算法。

Minor GC和Full GC一样吗

  1. Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特
    性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
  2. Full GC 又称为 老年代GC或者Major GC : 指发生在老年代的垃圾收集。出现了Major GC,经常会伴随至
    少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。
    Major GC的速度一般会比Minor GC慢10倍以上。

JMM

1.什么是JMMbr> JMM: (Java Memory Model的缩写),java内存模型,JVM定义了一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果
2.它干嘛的br> 作用:缓存一致性协议,用于定义数据读写的规则
JMM定义了线程工作内存和主内存之间的抽象关系:Java内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。线程、主内存、工作内存三者的交互关系如下所示

JVM(Java虚拟机)(由生到熟)
Java内存模型中定义了如下8种操作来完成。JVM实现时必须保证下面提及的每一种操作的原子的、不可再分的。
  • lock(锁定) : 作用于主内存的变量,它把一个变量标识为一条线程独占的状态
  • unlock(解锁) : 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取) : 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入) : 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用) : 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎。
  • assign(赋值) : 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量。
  • store(存储) : 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便后续的write操作使用。
  • write(写入) : 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

Java内存模型的三大特性 :

  • 原子性 :
  • 可见性 : 可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。volatile、synchronized、final三个关键字可以实现可见性。
  • 有序性 : 如果在本线程内观察,所有的操作都是有序的;如果在线程中观察另外一个线程,所有的操作都是无序
    的。前半句是指”线程内表现为串行”,后半句是指”指令重排序”和”工作内存与主内存同步延迟”现象。

回收方法区(重点)

方法区(永久代)的垃圾回收主要收集两部分内容 : 废弃常量和无用的类。
回收废弃常量和回收Java堆中的对象十分类似。以常量池中字面量(直接量)的回收为例,假如一个字符串”abc”已经进入了常量池中,但是当前系统没有任何一个String对象引用常量池的”abc”常量,也没有在其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个”abc”常量会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。判定一个类是否是”无用类”则相对复杂很多。类需要同时满足下面三个条件才会被算是”无用的类” :

  1. 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法
    JVM可以对同时满足上述3个条件的无用类进行回收,也仅仅是”可以”而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来防止永久代的溢出。

垃圾收集器

CMS收集器(老年代收集器,并发GC)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器、CMS收集器是基于“标记—清除”算法实现的,整个过程分为4个步骤:

  • 初始标记(CMS initial mark) 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需
    要“Stop The World”。
  • 并发标记(CMS concurrent mark) 并发标记阶段就是进行GC Roots Tracing的过程。
  • 重新标记(CMS remark) 重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变
    动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的
    时间短,仍然需要“Stop The World”。
  • 并发清除(CMS concurrent sweep) 并发清除阶段会清除对象。

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点: CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停顿。
缺点:
1、CMS收集器对CPU资源非常敏感 。
2、CMS收集器无法处理浮动垃圾 CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败
而导致另一次Full GC的产生。
3、会产生内存碎片

调优: -Xms8m -Xmx8m -XX :+PrintGcDetails
-Xms设置初始化内存分配大小/164-Xmx没置最大分配内存,默认 1/4
-XX :+PrintGCDetails 1/打 HlGc应圾回收信息
-XX: +HeapDumponoutOfMemoryError/ /oom DUMP

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91437 人正在系统学习中

来源:无心浪人

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年4月28日
下一篇 2021年4月28日

相关推荐