Java知识点梳理 JVM

Java 内存区域详解

一 运行时数据区域

1.1 程序计数器
程序计数器是唯一一个不会出现outOfMemoryError的内存区域。

  1. 字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,从而实现代码的流程控制。
  2. 在多线程的情况下,程序计数器能记录当前线程的执行位置,从而在线程切换回来的时候能恢复到上一次的执行位置。

1.2 虚拟机栈
虚拟机栈由一个个的栈帧组成,每个栈帧中都拥有局部变量表、操作数栈、动态链接、方法出口信息。
局部变量表存放了编译器可知的基本数据类型和对象引用。

1.3 本地方法栈
本地方法栈和虚拟机栈相似,在HotSpot中这两个栈合二为一。
本地方法栈为虚拟机使用到的Native方法服务。
本地方法执行的时候会在本地方法栈创建一个栈帧,用于存放本地方法的局部变量表、操作数栈、动态链接、方法出口信息,
在执行完毕之后,相应的栈帧会出栈并释放内存。

1.4 堆
用于存放对象实例,几乎所有对象实例和数组都在这里分配内存。
是垃圾回收的主要区域,所以也称GC堆。
现在的垃圾收集器都采用分代垃圾回收算法,所有堆又可以分为:年轻代、老年代、永久代。
年轻代又分为Eden区,Survivor区。
Eden区采用标记清除算法,再经过一次垃圾回收之后,存活的对象会进去Survivor区。
Survivor区采用复制算法,根据内存方向氛围From区和To区
在Survivor区经历若干次垃圾回收仍然存活的对象会进入老年代。

1.5 方法区
存放已被虚拟机加载的类信息、常量、动态变量、即时编译器编译后的代码等信息

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

1.7 直接内存

二 HotSpot虚拟机对象

2.1 对象的创建

  1. 类加载检查
  2. 分配内存
  3. 初始化零值
  4. 设置对象头
  5. 执行init方法

2.2 对象的内存布局
在HotSpot虚拟机中,对象内存分成三块:对象头、实例数据、对齐填充

2.3 对象的访问定位

  1. 使用句柄
  2. 直接指针

JVM 垃圾回收

一 JVM内存分配和回收

1.1 对象优先在Eden分配

主流的垃圾回收器采用分代垃圾回收算法,所以需要将堆内存分为年轻代和老年代
大多数情况下,对象在Eden区分配,当Eden区没有足够的空间进行分配时,虚拟机会发起Minor GC。

1.2 大对象直接进入老年代

为什么?
假设没有直接将大对象放入老年代,

1、如果这个大对象最后会进入老年代,那他就会经过Survivor区的多次复制算法,大对象在内存中来回复制会消耗很多时间。

2、如果这个大对象最后不进入老年代,新生代中的对象大部分都是朝生夕死的,相比普通大小的对象被分配到新生代,大对象无疑会让GC提前发生。
如果这个大对象不是朝生夕死的,那他就会进入Survivor区。
默认情况下,Survivor区不会分配太大的内存,那当大对象占据了大部分的空间之后,就会因为新生代GC的时候Survivor区内存不够导致大部分对象进入老年代,这就加快了老年代GC发生的时间,
而老年代GC对系统的影响大于新生代GC。

1.3 长期存活的对象进入老年代

虚拟机给每个对象一个对象年龄计数器。

如果对象分配到Eden区后经过一次Minor GC之后存活,并且Survivor区有足够的内存容纳这个对象,那么这个对象就会进入Survivor区。

进入Survivor区,对象的年龄为1,Survivor区的垃圾收集算法位复制算法。之后每次年轻代GC都会让对象的年龄加1,当年龄达到阈值(默认15)就会进入老年代。

1.6 空间分配担保

在年轻代进行Minor GC之前会先确保老年代有足够的内存空间容纳年轻代的所有对象。

二 如何判定对象是否失效

1、引用计数法

给每个对象分配一个计数器,每当它被引用一次,计数器加1;当引用时效,计数器-1;

当计数器为0的时候,这个对象就失效了。

但是它很难解决对象之间存在相互循环引用的问题。

2、可达性分析算法

通过一系列GC Root作为起点向下搜索,节点走过的路径称为引用链。

当一个对象到GC Root没有任何引用链的话,证明这个对象是时效的。

可以作为GC Root的对象有:

  • 虚拟机栈和本地方法栈中引用的对象

  • 方法区中静态属性和常量所引用的对象

  • 被同步锁持有的对象

引用类型

1、强引用

当一个对象具有强引用,虚拟机就算OOM,也不会回收强引用对象。

2、弱引用

当内存足够的时候,垃圾回收器不会回收软引用对象;如果内存不够了,垃圾回收期就会回收它。

3、软引用

不管内存是否足够,垃圾回收器都会回收它。

4、虚引用

形同虚设

唯一的作用是追踪垃圾回收的活动。

如何判断一个常量是废弃常量

假如字符串常量池中存在字符串"abc",如果没有任何String对象引用该字符串常量,就说明字符串"abc"是废弃常量

如何判断一个类是无用的类

  • 在堆中不存在类的实例对象

  • 类对应的ClassLoader已经被回收

  • 该类对应的java.lang.Class对象没有在任何地方被引用,
    无法在任何地方通过反射访问你该类的方法

垃圾收集算法

1、标记-清除算法

该算法分为标记和清除两个阶段,首先标记处所有不需要回收的对象,在标记完成后统一回收所有没有被标记的对象。

存在的问题:

  • 效率低
  • 标记清除会产生大量的不连续内存碎片

2、标记-复制算法
将内存分成大小相同的两块,每次使用其中的一块。当一块内存满的时候,将存活的对象复制到另一块内存中,然后把该内存全部清空。

每次回收都是对内存区间一半的回收。

3、标记-整理算法
标记所有不需要回收的对象,然后所有被标记的对象向一段移动,然后直接清理掉边界以外的内存

4、分代收集算法
根据对象存活周期的不同,将内存分为几块。一般讲Java堆分为年轻代和老年代。

根据垃圾收集器的不同,每个内存会有相应的垃圾收集算法。

一般来说年轻代使用标记-复制算法,老年代使用标记-清除或者标记-整理。

垃圾收集器

1、Serial
单线程收集器,只会使用一条垃圾收集线程去完成垃圾收集。

当进行垃圾收集工作的时候其他工作线程必须暂停,直到它收集结束。

年轻代使用标记-复制算法,老年代使用标记-清除或者标记-整理。

2、ParNew

Serial的多线程版本,除了使用多线程进行垃圾收集外,其它的行为和Serial收集器一致(工作线程)

3、Parallel Scavenge

Parallel Scavenge 收集器关注点是吞吐量(CPU中用于运行用户代码的时间和CPU总消耗时间的比值)。

4、CMS(Concurrent Mark Sweep)

CMS收集器的关注点是用户线程的停顿时间,注重用户体验。

实现了垃圾收集线程和用户线程基本上同时工作。

CMS收集器是标记-清除算法的一种实现,他的运作分成4个步骤:

  • 初始标记:暂停所有的其他线程并记录下与root相连的对象
  • 并发标记:同时开启GC线程和用户线程,用一个闭包结构去记录可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。算法里会跟踪这些发生引用更新的地方。
  • 重新标记:为了修正并发标记期间因为用户线程运行而导致标记产生变动的那部分对象的标记记录。
  • 并发清除:开启用户线程,同时GC线程清除未标记的对象

优点是并发收集,低停顿
缺点是对CPU资源敏感;无法处理浮动垃圾;使用标记-清除算法会生成大量的空间碎片

5、G1

G1是面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时还具备高吞吐量的性能特征。
它具备以下特点:

  • 并行与并发 使用多个CPC和多核的硬件优势来缩短Stop-The-World的时间。其他收集器原本需要停顿Java线程的执行的GC操作,G1可以使用并发的方式让java程序继续执行。
  • 分代收集 虽然G1不需要其他收集器配合就能独立管理整个GC堆,但还是保留了分代的概念
  • 空间整合 整体上采用标记-整理算法
  • 可预测的停顿 用户可以指定毫秒单位的停顿时间

类的加载过程

加载

  1. 虚拟机通过全类名查找对应的二进制字节码文件
  2. 字节流所代表的静态储存结构转换成方法区的运行时数据
  3. 在堆中生成该类的Class对象

验证

  1. 文件格式验证 : 验证字节流是否符合Class文件格式的规范
  2. 元数据验证:对字节码的描述进行语义分析,以保证描述的内容符合Java语言规范的要求
  3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法、符合逻辑的
  4. 符号引用验证:保证解析动作能正常执行

准备

为类变量分配内存并初始化零值。

解析

将符号引用替换为直接饮用。

初始化

执行方法初始化。

只有主动去实用类的时候,才会初始化:

  1. 执行new、getstatic、putstatic、invokestatic方法
  2. 使用java.lang.reflect对类进行反射调用
  3. 初始化一个类的时候,如果父类没有初始化,就会先初始化父类
  4. 虚拟机启动的时候,初始化main方法所在的类
  5. 使用MethodHandler、VarHandler进行轻量级的反射调用的时候,需要先调用findVarHandler方法初始化类。

类加载器

  • 启动类加载器BootStrapClassLoader
    最顶层类加载器,C++实现,加载%JAVA_HONE%/lib目录下的jar包和类或者被-Xbootclasspath参数指定的路径中的所有类

  • 拓展类加载器ExtensionClassLoader
    主要负责加载%JRE_HOME%/lib/ext目录下的jar包和类,或者被java.ext.dirs系统变量所指定的路径下的jar包

  • 应用程序类加载器AppClassLoader
    面向用户的加载器,负责加载当前应用classpath下的所有jar包和类

  • 用户自定义类加载器UserDefinedClassLoader

双亲委派模型

每个类都有一个对应的类加载器。系统中的类加载器默认使用双亲委派模型进行协作,即在加载类的时候会自底向上检查父类加载器是否加载了该类,因此所有的类加载请求最终会到启动类加载器中,然后自顶向下尝试加载类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值