JVM 要点概述

Java学习过程中的JVM概述

最近在学习java的过程中系统的了解了一下JVM原理,买了一本《深入了解JVM》结合视频的讲解对JVM有了一个大体上的认知。
1.Java的跨平台性就是依赖于JVM。JVM是一种标准,它有很多的实现,其中最常见的就是Oracle公司推出的Hotspot虚拟机。

Java虚拟机的内存结构:

线程独占的:
虚拟机栈,本地方法栈,程序计数器

线程共享的:
方法区(方法区的实现:永久代和元空间(jdk8之后元空间替代永久代)),堆空间

堆空间又分为:年轻代(分为:eden,s0,s1)和老年代

元空间是在本机的实际内存中开辟的,方便读写。而永久代是在Java虚拟机中开辟内存。

类加载器子系统
  1. 1.类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识。

    2.ClassLoader只负责class文件的加载,至于它是否运行则由ExcutionEngine(执行引擎)决定。

    3.加载的类信息存放于方法区中。除了类的信息外,方法区中还会存放运行时的常量池信息,可能还包括字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)

类加载的过程具体包括:加载,链接和初始化,其中链接阶段又分为:验证,准备和解析。
主要说一下链接的准备阶段(prepare)和解析阶段:
在准备阶段会为类变量分配内存和默认初始化,但是不包括final修饰的类变量,final修饰的类变量会在编译期就分配内存,并在prepare阶段显示初始化。这里不会为实例变量分配初始化,实列变量会随着对象的创建在堆中初始化。
解析阶段是将常量池中的符号引用转化为直接引用即指向实际的内存地址。

常见的类加载器:引用类加载器(加载Java的核心库),扩展类加载器,系统类加载器,用户自定义类加载器(扩展类和系统类也成为自定义类加载器)。

双亲委派机制:Java虚拟机是采用按需加载的模式将类加载到内存中的,也就是说只有当这个类被用到时类加载器才加载。如果一个类收到了类加载请求,他并不会 立即加载而是交给“父类”加载器来加载一直到引用类加载器,如果父类加载器可以加载就将结果返回,如果父类加载器不能加载则由当前类加载器加载。

双亲委派机制的存在很好的解决了类的重复加载和避免核心API被修改。

程序计数器

用来存储指向下一个指令的地址,即要执行的指令代码。由存储引擎读取下一条指令。
作用:因为cpu需要不停的切换各个线程,这时候切换回来之后就得知道接着从哪开始执行。JVM的字节码解释器就需要通过改变pc寄存器中的值来明确下一条应该执行什么样的字节码指令。

虚拟机栈

每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的方法调用。
作用:主管Java程序的运行,它保存方法的局部变量,部分结果,并参与方法的调用和返回。

栈帧的内部结构

1.局部变量表 2.操作数栈 3.动态链接(或指向运行时常量池的方法引用)
4.方法返回地址 5.一些附加信息

局部变量表:
· 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型,对象引用,以及returnAddress类型。
· 局部变量表所需的容量大小在编译期确定下来。
· 局部变量表,最基本的存储单位是slot(变量槽)
· 如果当前栈帧是由构造方法或实例方法创建的,那么该对象的引用this将会 存放在index为0的slot处,其余的参数 按照参数列表顺序排列。
· 局部变量表中的变量也是重要的垃圾回收的根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。

动态链接:
每一个栈帧内部都包含一个指向运行时常量池中栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。
作用:动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

常量池的作用就是为了提供一些符号和常量,便于指令的识别。

堆空间

· 一个进程对应一个JVM实例,一个JVM实例只存在一个堆内存。
· Java堆区在JVM启动时被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。(堆和栈的大小都可以通过参数调节)。
· 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
· 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区。
· Java 8及以后堆内存在逻辑上分为三部分:新生区 + 养老区 + 元空间
· 堆空间会发生溢出异常:OutOfMemory(OOM)
· 几乎所有new的对象都要先放在伊甸园区。当伊甸园去区的空间被填满时,会触发垃圾回收(Minor GC)(只有当伊甸园区满时会触发),将不再被其他对象引用的对象进行垃圾销毁,在加载新对象到伊甸园区。然后将幸存下来的对象存入 servivor0 区;当再次触发垃圾回收时,如果s0区中的对象还没有被回收就放入servivor1 区。
· 什么时候能进入养老区呢 ?可以设置s0和s1区之间默认的跳转次数,默认15次。

年轻代GC触发机制

→ 当年轻代空间不足时就会触发MinorGC,每次MinorGC会清理年轻代的内存。Servivor区满并不会触发GC
→ 因为Java对象大多数具备朝生息死的特性(即用完就丢掉)所有MinorGC非常频繁,一般回收速度比较快。
→ MinorGC会引发STW即暂停用户线程,等垃圾回收结束,用户线程才恢复运行。

老年代GC(MojorGC/FullGC)触发机制

→ 发生在老年代的GC,对象从老年代消失时,就认为“MinorGC或者FullGC”发生了。
→ MojorGC的速度一般会比MinorGC慢10倍以上,STW的时间更长。
→ 如果MojorGC后,内存还不足,就会OOM。

堆空间分代思想

分代的唯一理由就是优化GC性能,能方便快速的找出朝生夕灭的对象。
内存分配策略:
①对象优先分配到Eden;②大对象直接分配到老年代;③长期存活的对象分配到老年代;④动态对象年龄判断(如果Survivor区中相同年龄的所有对象的总和大于Survivor空间的一半,年龄大于或等于改年龄的对象可以直接进入老年代,避免S0和S1之间来回跳转)

TLAB

· 从内存模型而不是垃圾回收的角度,堆Eden区域继续进行划分,JVM为每个线程分配一个私有的缓存区域,它包含在Eden空间内。
· 多线程同时分配 内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此可以将这种内存分配方式称之为快速分配策略。
· 虽然TLAB仅占Eden的百分之一,但是分配对象优先考虑在TLAB中。

方法区

1.方法区的大小跟堆空间一样,可以选择固定大小或者可扩展。方法区的大小决定了系统可以保存多少各类。

2.方法区用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等(JIT代码缓存 →执行引擎会将热点代码编译成机器指令直接存在方法区,减少编译次数。

3.常量池存在与字节码文件中。一个有效的字节码文件中除了包含各种类的版本信息,字段,方法及接口等描述信息外,还包含一项信息,那就是常量池表,包含各种字面量和对类型,域和方法的符号引用。

为什么需要常量池:节省空间,用符号引用可以减少字节码指令的个数,增加代码复用性。

· 运行时常量池:此时不再是常量池中的符号引用,而是换为真实地址。

4.永久代为什么要被元空间替换?
Ⅰ.因为永久代不占用直接内存,所以对永久代设置空间大小时是很难确定的。如果动态加载类过多很容易OOM。
Ⅱ.对永久代的调优是很困难的。

执行引擎

JVM 的主要任务是负责装载字节码文件到其内部,当字节码并不能直接运行在操作系统上,以为字节码指令并非等价于本地机器指令,它内部包含的仅仅只是一些能够被JVM所识别的字节码指令,符号表,以及其他辅助信息。

如果想让一个Java程序运行起来,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM中的执行引擎充当了将高级语言翻译为机器指令的任务。
· Java是半编译半解释型语言:JVM在执行Java代码的时候,通常都会将解释执行与编译执行二者结合起来一起进行。

热点代码及探测方式:
目前HotSpot VM所采用的热点探测方式是基于计数器(方法调用计数器)的热点探测。热点代码会触发JIT编译。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值