JVM详解


JVM内存区域详解


一个进程中可以包含多个线程
一个进程中属于线程私有的部分:程序计数器、虚拟机栈、本地方法栈
一个进程中属于线程共享的部分:堆、元空间、直接内存(非运行时数据取的一部分)

在这里插入图片描述

程序计数器:可以看做是当前线程所执行的字节码的行号指示器,主要有两个作用:1、字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制;2、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡


java虚拟机栈:方法调用的数据需要通过虚拟机栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址,且具有先进后出的数据结构,只支持入栈和出栈两种操作。
虚拟机栈中可能会出现的两种错误:
StackOverFlowError: 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
OutOfMemoryError: 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

在这里插入图片描述

本地方法栈:本地方法栈的作用与虚拟机栈相似,区别是== 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务==


堆:java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存
Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆,从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间。下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
JDK8之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存

在这里插入图片描述
在这里插入图片描述

方法区:JVM运行时数据区域的一块逻辑区域,是个线程共享的内存区域。当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
JDK 8之后使用元空间 (MetaSpace) 替代永久代 (PermGen) 的原因:1、整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制;2、元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
元空间的方法参数:-XX:MetaspaceSize=N(设置初始大小);-XX:MaxMetaspaceSize=N (设置最大值)
字符串常量池:是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

在这里插入图片描述

JDK7将字符串常量池移动到堆中的原因:主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。


Java对象创建过程:
1、类加载检查:虚拟机遇到new命令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2、内存分配:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。分配方式有 “指针碰撞” 和 “空闲列表” 两种。
3、初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)
4、设置对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中
5、执行int方法:一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。


类加载过程详解


一个类的生命周期包括:加载----》连接----》初始化----》使用----》卸载;其中连接又包含验证、准备、解析三个步骤。

在这里插入图片描述

类加载过程:系统加载.class文件的三个步骤加载、连接和初始化
加载:在内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口;
连接过程:包含验证、准备与解析,其中验证包含文件格式验证(字节流师傅符合Class文件格式)、元数据验证、字节码验证和符号引用验证;准备阶段是正式为类变量分配内存并设置类初始变量值的阶段;解析是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化:初始化阶段是执行初始化方法 ()方法的过程;对于 () 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞,并且这种阻塞很难被发现。


卸载:卸载类即该类的Class对象被GC
卸载类需要满足三个要求:
该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
该类没有在其他任何地方被引用
该类的类加载器的实例已被 GC


JVM 垃圾回收详解


Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。现在的收集器基本都采用分代垃圾手机算法,所以Java堆被划分为几个不同的区域,根据区域特点选择合适的垃圾收集算法。
JDK1.7及之前,堆内存结构:新生代内存、老生代、永久代
JDK1.8及之后,堆内存结构:新生代内存、老生代、元空间。即JDK8之后,永久代被元空间取代,元空间使用的是直接内存。

在这里插入图片描述
在这里插入图片描述

内存分配和回收原则
1、多数情况下,对象在新生代中的Eden区分配,Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,通过分配担保机制将新生代中的对象提前转移到老年代中去。(若老年代上的空间不足以存放转移对象,则会发起Full GC命令)
2、大对象直接进入老年代。比如字符串、数组这些需要大量连续内存空间的对象会直接进入老年代,避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
3、长期存活的对象将进入老年代,虚拟机给每个对象一个对象年龄(Age)计数器,如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当超过设定年龄(默认15,或通过-XX:MaxTenuringThreshold进行设置)就会进入老年区


HotSpot VM里面的GC可以分为两大种:
1、Partail GC(非手机整个GC堆的模式):包含有Old GC(只收集Old gen的GC)、Young GC(只收集young gen的GC)、Mixed GC(收集整个young gen以及部分Old gen的GC)
2、Full GC(收集整个堆)
分代式GC策略:
young GC:当young gen中的eden区分配满的时候触发,young GC中有部分存活的对象会晋升到old gen,所以young GC后old gen的占用量会有所升高。
full GC:当触发一次young GC时,若young GC的平均晋升大小比old gen剩余的空间大,则不会触发young GC而是转而触发 full GC.



总结:
部分收集 (Partial GC):
1、新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
2、老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
3、混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
整堆收集 (Full GC):收集整个 Java 堆和方法区
空间分配担保:确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。


对象死亡判定方法


在堆中几乎存放着所有对象实例,对堆进行垃圾回收前的第一步是判定哪些对象已经死亡
方法1:引用计数法
给对象添加一个引用计数器,每当有一个地方引用它,计数器+1;当引用失效,计数器-1;当计数器为0的对象就是不可能再被使用的。
该方法实现简单效率高,但虚拟机并不是使用该方法管理内存,原因是难以解决对象间相互循环引用的问题
方法2:可达性分析法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

在这里插入图片描述

哪些对象可以作为GC Roots?
虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈(Native 方法)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
所有被同步锁持有的对象
对象可以被回收就代表一定会被回收吗?
对于可达性分析法中不可达的对象,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。


引用类型总结(强度逐渐减弱)
强引用:最普遍的引用,如果一个对象是强引用,垃圾回收器绝不会回收它,当内存空间不足时就会抛出 OutOfMemoryError 错误;
软引用:一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
弱引用:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。


如何判定一个类是无用类(满足以下三个条件):
1、该类的所有实例都已经被回收,及Java堆中不存在该类的任何实例
2、加载该类的ClassLoader已经被回收;
3、该类对应的java.lang.Class对象没有在任何地方配引用,无法在任何地方通过反射访问该类的方法


垃圾收集算法:


方法1:标记-清除算法
该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
该方法存在的问题:1、效率问题;2、空间问题(标记清除后悔产生大量不连续碎片)

在这里插入图片描述


方法2:标记-复制算法
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。(解决了效率问题,但降低了内存利用率)

在这里插入图片描述

方法3:标记-整理算法
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

在这里插入图片描述

方法4:分代收集算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值