JVM复习

JVM的内存区域

  • 程序计数器:线程私有。该区域是唯一一个在Java虚拟机规范中没有规定任何OOM情况的内存区域。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令。( 记住 当前线程 的下一条 JVM字节码指令 的 执行地址 ,便于进行 线程切换)
  • Java虚拟机栈:线程私有。
    • 栈帧:存储了局部变量表、操作数栈、动态连接和方法出口等信息。局部变量表存放了编译器可知的各种基本数据类型(int、short、byte、char、double、float、long、boolean)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一跳字节码指令的地址)。
  • 本地方法栈:线程私有。调用本地方法时使用的栈。Native方法不是以Java语言实现的,而是以本地语言实现的(c或c++)与操作系统底层交互。
  • :线程共享。在堆上的区域会被进一步划分为新生代和老年代等。Java虚拟机在启动的时候,可以使用Xmx参数指定堆区大小。主要存储使用new关键字创建的对象。OOM故障最主要的发源地。
  • 方法区:线程共享。包括运行时常量池。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。常量池中存储各种字面量和符号引用,避免了频繁的创建和销毁对象,实现了对象的共享。
    • JDK8之前,Hotspot中方法区的实现是永久代,JDK7开始把原本放在永久代的字符串常量池、静态变量等移出到堆,JDK 8 开始去除永久代,使用元空间(Metaspace),永久代剩余内容移至元空间,元空间直接在本地内存分配。
    • 为什么要将永久代替换成Metaspace:字符串存在永久代中,容易出现性能问题和内存溢出。类方法的信息等比较难确定其大小,对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。永久代会为GC带来不必要的复杂度,并且回收效率偏低。Oracle可能会将HotSpot与JRockit合二为一。
  • 直接内存:直接内存并不属于Java规范规定的属于Java虚拟机运行时数据区的一部分。Java的NIO可以使用Native方法直接在java堆外分配内存,使用DirectByteBuffer对象作为这个堆外内存的引用。

堆和栈的比较

  • 堆主要存放对象,栈主要执行程序
  • 堆可以动态分配内存,生存期不受编译器控制,灵活性好,但存取速度慢
  • 栈中数据大小与生存期有关,缺乏灵活性

一文搞懂JVM内存结构!真的写的好!

怎么判断一个对象是否能被回收——GC Roots

垃圾的定义

内存中不再被使用的空间就是垃圾,当一个对象的地址没有变量去引用时,该对象就会成为垃圾对象

如何检验是否被回收

可以重写 Object 类中的 finalize 方法,这个方法在垃圾收集器执行的时候,被收集器自动调用执行的

怎样通知垃圾收集器回收对象

调用 System 类的静态方法 gc(),通知垃圾收集器去清理垃圾,但不能保证收集动作立即执行,具体的执行时间取决于垃圾收集的算法

怎么判断一个对象是否能被回收

程序计数器、虚拟机栈、本地方法栈。这几个区域完全不用管回收问题,因为方法结束或者线程结束的时候他们所占用的内存就自然跟着一起释放了,3个区域随线程而生,随线程而灭。所以我们只需要管堆和方法区。

  • 引用计数法:通过给对象添加计数器,每当有一个地方引用它,计数器值就 + 1,有一个地方引用失效就,计数器值就 - 1。只要计数器值为零,就说明这个对象不再被使用,可以当作垃圾回收。
    • 优缺点:实现简单。引用计数器本身需要维护,也会有一定损耗。解决不了循环引用的问题。
  • 枚举根节点(GC Roots)做可达性分析(根搜索路径):以”GC Roots(Tracing Root)“的对象作为起始点,向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用,可以被回收。
  • 可作为GC Root的对象:
    • 虚拟机栈中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(native方法)中引用的对象

JVM之GC Roots概述

垃圾回收机制

jvm内存结构:分为年轻代和老年代。年轻代又分为Eden和survivor区(又分为from和to两个区)。Eden:from:to=8:1:1。

  • 新生代对象在Eden区产生且优先分配在Eden区。当Eden没有足够空间时,将发生Minor GC。
  • 当Eden区满了或放不下了,这时候其中存活的对象会复制到from区。(如果存活下来的对象from区都放不下,则这些存活下来的对象全部进入老年代。之后Eden区的内存全部回收掉。
  • 之后产生的对象继续分配在Eden区,当Eden区又满了或放不下了,这时候将会把Eden区和from区存活下来的对象复制到to区。(如果存活下来的对象to区都放不下,则这些存活下来的对象全部进入年老代,之后回收掉Eden区和from区的所有内存(此时from区是空的,然后将from区和to区交换,即保持to区为空, 如此往复。)。
  • 会有很多对象会被复制很多次(每复制一次,对象的年龄就+1),默认情况下,当对象被复制了15次(这个次数可以通过:-XX:MaxTenuringThreshold来配置),就会进入年老代了。
  • 当年老代满了或者存放不下将要进入年老代的存活对象的时候,就会发生一次Full GC(这个是我们最需要减少的,因为耗时很严重)。

什么时候开始回收

GC并不是等变量为空就回收内存,而是超出变量的作用域后就会被自动回收。

垃圾收集器

串行收集器

使用单线程进行垃圾回收的收集器,每次回收时,串行收集器只有一个工作线程,对于并行能力较弱的计算机来说,串行收集器的专注性和独占性往往有更好的性能表现。串行收集器可以在新生代和老年代中使用,根据作用于不同的堆空间,分为新生代串行收集器和老年代收集器。

  • Serial收集器:新生代串行收集器,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程,是JVM client模式下默认的新生代收集器。Serial收集器没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。
  • Serial Old收集器 :Serial收集器的老年代版本,使用标记-整理算法。给Client模式下的虚拟机使用,如果在Server模式下
    • 一、在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
    • 二、作为CMS收集器的后备预案,在并发收集发送Concurrent Mode Failure时使用。

并行收集器

  • ParNew收集器:Serial收集器的多线程版本
  • Parallel Scavenge收集器:新生代使用并行回收收集器,老年代使用串行收集器
    • 吞吐量优先收集器,目标是达到一个可控制的吞吐量(Throughput)。
  • Parallel Old收集器:新生代和老年代都使用并行回收收集器
    • Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
  • CMS收集器:以获取最短回收停顿时间为目标的收集器。
    • 非常符合互联网站或者B/S系统的服务端上,重视服务的响应速度,希望系统停顿时间最短的应用
    • 基于“标记—清除”算法实现的
    • CMS收集器的内存回收过程是与用户线程一起并发执行的
    • 初始标记:标记GC Roots能直接关联到的对象,速度很快
    • 并发标记:进行GC Roots Tracing的过程
    • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,但远比并发标记的时间短
    • 并发清除
    • 优点:并发收集、低停顿
    • 缺点:对CPU资源非常敏感、无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。一款基于“标记—清除”算法实现的收集器
  • G1(Garbage-First)收集器:当今收集器技术发展的最前沿成果之一,G1是一款面向服务端应用的垃圾收集器。
    • 优点:并行与并发:充分利用多CPU、多核环境下的硬件优势
    • 分代收集:不需要其他收集器配合就能独立管理整个GC堆
    • 空间整合:“标记—整理”算法实现的收集器,局部上基于“复制”算法不会产生内存空间碎片
    • 可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
    • 步骤:初始标记:标记一下GC Roots能直接关联到的对象,需要停顿线程,但耗时很短
    • 并发标记:是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行
    • 最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
    • 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划

参考

Minor GC、Major GC和Full GC

  • Minor GC:对新生代进行回收,不会影响到年老代。因为新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。
  • Major GC:清理老年代。发生 Major GC 一定会发生一次Minor GC
  • Full GC:对整个堆进行回收,包括新生代和老年代。由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数,导致Full GC的原因包括:老年代被写满、永久代(Perm)被写满和System.gc()被显式调用等。

四种垃圾回收算法——分代收集算法

  • 标记清除:从根集合节点开始先标记所有存活对象,然后清除所有未被标记的对象
    • 适用于:存活对象较多的情况,适用于老年代(旧生代)
    • 缺点:容易产生生存碎片,(来一个新的对象比所有空闲表都大),这样容易提前触发垃圾回收。扫描了整个空间两次(一次标记,一次清除)
  • 复制算法:从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存上去,之后将原来的那一块儿内存全部回收掉。
    • 适用于:存活对象较少的情况。使用与年轻代(新生代)。只扫描了整个空间一次。
    • 缺点:需要额外的一块空的内存空间。需要复制移动对象。
  • 标记整理:从根节点开始对存活对象进行标记,并将所有存活对象压缩到内存的一段,清理边界所有空间。
    • 优点:避免了碎片的产生,不需要两块相同的内存空间,性价比比较高。
  • 分代收集算法:不同年代使用不同的算法。新生代存活率低,使用复制算法,老年代对象存活率高,使用标记清除或标记整理。

垃圾回收算法——分代收集算法

GC算法优劣标准

  • 吞吐量越高算法越好
    JVM在专门的线程[GC Threads]中执行GC 只要GC线程是活动的 就会和应用程序线程[Application Threads]争用当前可用CPU的时钟周期而吞吐量就是指应用程序线程占程序总用时的比例
    吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
  • 暂停时间越短算法越好
    一个时间段内应用程序线程让GC线程执行而完全暂停。这样可以终止所有应用线程,不会产生新的垃圾,并且保证了系统状态在某一瞬间的一致性,有益于更好的标记垃圾对象。

类加载过程

浅析类的加载:通过一个类的全限定名获得此类的统一资源定位符

双亲委派机制

JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
在这里插入图片描述
注意:

  • 类加载器代码本身也是java类,因此类加载器本身也是要被加载的,因此显然必须有第一个类加载器不是Java类,这就是bootStrap,是使用c++写的其他这是java了。
  • 虽说bootStrap、extclassLoader、appclassloader三个是父子类加载器关系,但是并没有使用继承,而是使用了组合关系
  • 优点
    • 保证程序安全,防止核心API被随意篡改:具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,可以比较笼统的说像jdk自带的几个jar包肯定是位于最顶级的,再就是我们引用的包,最后是我们自己写的,保证了java程序的稳定性。
    • 避免类的重复加载

谈谈双亲委派

沙箱机制

我们创建一个自定义string类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

打破双亲委派机制

  • 双亲委派机制的弊端:有时候不希望委派到父类去加载,希望直接在本类就加载。

Tomcat打破双亲委派机制

引用的分类

  • 强引用:必不可少,不会被垃圾回收器回收的对象
  • 软引用:有用但不必需,只有在JVM内存不足时才被回收
  • 弱引用:非必需对象,一旦被发现就会被回收(但不一定立马被发现)
  • 虚引用:相当于没有引用,任何时候都无法通过引用取到对象值

强引用、软引用、弱引用、虚引用

OOM

java.lang.OutOfMemory 属于Error

谈谈对OOM的认识

Java内存溢出和泄露

深入分析编译原理

深入分析编译原理

逃逸分析

逃逸分析

JVM原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值