Java笔记-JVM初识

1、JVM

1.1 JVM内存结构

JDK7内存模型

Java笔记-JVM初识_第1张图片

JDK8内存模型

Java笔记-JVM初识_第2张图片

JVM内存结构:

其中线程私有的是(1)(2)(3)

(1)PC寄存器(程序计数器):
a.每个线程都有,为了在多线程切换时,回到自己之前的位置
b.寄存器里边指定了下一条需要执行的指令
c.执行Java代码时,保存当前指令的地址
d.不会有OOM的情况
e.native方法为空
(2)Java虚拟机栈

1.每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。
2. 之前我们一直讲的栈区域实际上就是此处的虚拟机栈,再详细一点,是虚拟机栈中的局部变量表部分。
3.此区域一共会产生以下两种异常:
1. 如果线程请求的栈深度大于虚拟机所允许的深度(-Xss设置栈容量),将会抛出StackOverFlowError异常。
2. 虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM(OutOfMemoryError)异常

每个方法执行时创建一个栈帧用于存储:
a.局部变量表:8种基本数据类型、对象引用、字节码指令地址。内存在编译期间分配
b.操作数栈:从局部变量种取出数据,深度在编译期间确定
c.动态链接:符号引用
d.方法返回地址:方法出口
(3)本地方法栈
与虚拟机栈作用一样,区别是本地方法栈执行Native方法,而虚拟机栈
为JVM执行的Java方法服务。

线程共享区域(4)(5)

(4)Java堆
a.在JVM启动时创建,所有的对象实例以及数组都要在堆上分配。
b.如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM。
c.可以动态扩展OOM(年轻代、老年代、永久代-元空间)
(5)方法区/元数据区
a.用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
b.此区域的内存回收主要是针对常量池的回收以及对类型的卸载。当方法区无法满足内存分配需求时,将抛出OOM异常。
(6)运行时常量池-方法区内
a.编译期及运行期间产生的常量被放在运行时常量池中。
b.这里所说的常量包括:基本类型、包装类(包装类不管理浮点型,整形只会管理-128127)和String。
c.类加载时,会查询字符串常量池,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

常量池补充说明
常量池可以分为 Class文件常量池、运行时常量池、字符串常量池:


a.Class文件常量池
    Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

b.运行时常量池
    运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

c.字符串常量池
    存储字符串对象,或是字符串对象的引用。
(7)直接内存
a.在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

b.直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。也可能导致OutOfMemoryError异常出现。

1.2 JVM垃圾回收机制GC

**JVM垃圾回收发生在堆和方法区,Java进程在启动后会创建垃圾回收线程,来对内存中无用的对象进行回收。
**

1.2.1 如何判断对象已死(垃圾)

(1)引用计数法

给对象分配一个引用计数器,每当有地方引用它时,计数+1,当引用失效时,计数-1。当某个对象的计数为0时,就不能被引用了,人认为对象已死。
缺点:当两个对象循环引用时,无法回收

(2)可达性分析法

任务所有对象都是从“GC Roots”的对象作为起始点(树根节点),从这些节点出发向下搜索,可以遍历到的对象就是“可达对象”,遍历的路径称为“GC Roots引用链”。当某个对象与GC Roots之间没有引用链时,就认为该对象不可达,这些对象会被认为是可以回收的对象
Java笔记-JVM初识_第3张图片

1.2.2 垃圾回收算法

(1)复制引用法-新生代的收集算法

将内存分为容量相等的两块,每次只使用其中一块,当这一块用完了,就将还活着的对象复制到另外一块上,然后把已使用的内存空间一次性清理掉
优点:实现简单,因为每次都是堆整个半区进行回收,因此就不会产生内存碎片
缺点:内存利用率低,只能用一半

(2)标记清除法-老年代收集算法(基础算法)

(1)先标记出要回收的对象
(2)标记完成后,统一回收这些对象
优点:不需要额外的空间
缺点:回产生内存碎片

(3)标记整理法-老年代收集算法

(1)先标记出要回收的对象
(2)标记完成后,统一回收这些对象
(3)让存活的对象向一端移动

(4)分代收集算法
  • 当前JVM垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
  • 一般是把Java堆分为新生代和老年代。
  • 朝生夕死:在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;
  • 老年代中对象存活率高,垃圾少、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。

2、垃圾回收过程

  • 新生代“朝生夕死”,存活对象少,不需要按照复制算法将内存进行1:1划分,而是将新生代内存划分为Eden(伊甸园)和两块较小的Survivor(幸存者)空间【一个from、一个to】,每次使用Eden和其中一块Surivor。Eden:Surivior From:Surivior To=8:1:1
  • Eden空间不足,触发Minor GC:用户线程创建的对象优先分配在Eden区,当Eden空间不足时,触发Minor GC:将Eden和Surivor中还存活的对象一次性复制到另一块Surivor空间,最后清理Eden和用过的Survivor空间
  • 垃圾回收结束后,用户线程又开始创建新对象并分配在Eden区,当Eden区空间不足时,重复以上步骤进行Minor GC
  • 年老对象晋升到老年代:长期存活的对象(15次)将进入老年代,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且把对象年龄设为1。对象在Survivor空间中每"熬过"一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将晋 升到老年代中。
  • Surivior空间不足,存活对象通过分配担保机制进入老年代:Survivor区只占新生代10%空间,没有办法保证每次回收都有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保。如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代
  • 老年代空间不足,触发Major GC

3、内存分配与回收策略

(1)对象优先在Eden区分配
(2)大对象直接进入老年代:大量来纳许空间的对象,如很长的字符串以及数组
(3)长期存活的对象将进入老年代MaxTenuringThreshold
(4)动态判定对象年龄,并不总是按照MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半年龄大于或等于该年龄的对象就可以直接进入老年代
(5)空间分配担保:

  • 在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。
  • 如果大于,则此次Minor GC是安全的。
  • 如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
  • 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;
  • 如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC

3、Java8中字符串常量池到底是在哪里?

今天看String\StringBuilder\StringBuffer时,遇到了字符串常量池这个名词,虽然之前听过,但没有细究过它到底是存在哪里。
于是在网上搜寻了一番,说的我都挺懵,但看到一篇自我感觉比较清晰详细的文章,在这里–》

Java笔记-JVM初识_第4张图片
总的来说就是,JDK1.7之前,运行时常量池(字符串常量池也在里边)是存放在方法区,此时方法区的实现是永久带。
JDK1.7字符串常量池被单独从方法区移到堆中,运行时常量池剩下的还在永久带(方法区)
JDK1.8,永久带更名为元空间(方法区的新的实现),但字符串常量池池还在堆中,运行时常量池在元空间(方法区)。
Java笔记-JVM初识_第5张图片
Java笔记-JVM初识_第6张图片

你可能感兴趣的:(Java,java,开发语言,JVM)