JVM的理解及总结

什么是JVM

JVM 称 java虚拟机 ,一般的用于学习的虚拟机 Hotspot
在这里插入图片描述

SUN的JDK版本从1.3.1开始运用HotSpot虚拟机, 2006年底开源,主要使用C++实现,JNI接口部分用C实现。
HotSpot是较新的Java虚拟机,用来代替JIT(Just in Time),可以大大提高Java运行的性能。
Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢。而HotSpot将常用的部分代码编译为本地(原生,native)代码,这样显着提高了性能。
HotSpot JVM 参数可以分为规则参数(standard options)和非规则参数(non-standard options)。
规则参数相对稳定,在JDK未来的版本里不会有太大的改动。
非规则参数则有因升级JDK而改动的可能。
规则和非规则参数这里不做介绍了,网上资料很多。

Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行。因此在运行时,Java源程序需要通过编译器编译成为.class文件。众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,下皆以windows平台为例,linux下和solaris下其实类似,为:libjvm.so),这个动态连接库才是java虚拟机的实际操作处理所在。

JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统运行时数据区,和执行引擎

JVM的位置

在这里插入图片描述

类装载器

每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的类装载器包括:根加载器(BootstrapClassLoader)扩展加载器(ExtensionClassLoader)应用类加载器(AppClassLoader)用户自定义类装载器,用户自定义类装载器是Java程序的一部分,必须是ClassLoader类的子类。
在这里插入图片描述

启动类加载器:
A、根加载器:

  1. 虚拟机的一部分,C++写的
  2. 加载 /lib或者Sbootclasspath指定的类
  3. 程序员不可调用,使用时传入参数null

B、扩展程序加载器:

  1. 加载 /lib/ext目录的类

C、应用程序加载器:

  1. 用户路径下classpath路径的类

双亲委派机制:
每次类加载首先向上委托APP-> Exc -> Boot去找。检查启动类加载器是否能够加载当前这个类,能加载就直接加载,否则抛出异常交给子加载器加载。
保证安全性:防止加载同一个class文件,通过委派,加载过的就不再加载防止数据丢失
保证唯一性:保证核心class文件的唯一性,通过委派,即使篡改了核心class文件,子加载器也不会加载。

执行引擎

主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 。

自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。

运行时数据区

分为: 方法区 (又称永久代,jdk8就被元空间替代) ,栈 , 本地方法栈 ,堆 , pc寄存器
草图:
在这里插入图片描述
在这里插入图片描述

方法区(Method Area)

线程共享
存放所加载类的信息、类中静态方法、静态变量、类中final定义的常量、类中的fieId信息、类中方法信息。
当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
方法区和永久代的关系

方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。

我们知道在HotSpot虚拟机中存在三种垃圾回收现象,minor GC、major GC和full GC。对新生代进行垃圾回收叫做minor
GC,对老年代进行垃圾回收叫做major GC,同时对新生代、老年代和永久代进行垃圾回收叫做full GC。许多major
GC是由minor GC触发的,所以很难将这两种垃圾回收区分开。major GC和full
GC通常是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major
GC”的时候一定要问清楚他想要指的是上面的full GC还是major GC。

元空间

HotSpot虚拟机在1.8之后已经取消了永久代,改为元空间,类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。永久代中的元数据的位置也会随着一次full
GC发生移动,比较消耗虚拟机性能。同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full
GC以及对以后的并发隔离类元数据等方面进行优化。

栈(Stack)调试命令(-Xss:如-Xss128k)

线程独享
每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及栈帧,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址
在这里插入图片描述

本地方法栈(Native Method Stack)

线程共享
存储本地方法调用的状态
在内存区域中 Native Method Stack 用于登记 native方法,在最终执行时通过JNI加载本地方法库中的方法

介绍native的关键字
1.带了native关键字说明java的作用域达不到,会去调用底层C语言的库。
2.会进入本地方法栈
3.调用本地方法的接口 JNI (扩展java的使用融合不同的编程语言为java使用)

堆(Heap)(-Xms / -Xmx — 堆的初始大小 / 堆的最大大小)

线程共享
一个 JVM 只有一个堆
堆中存放的东西:类,方法,常量,变量,保存我们所有引用类型的真实对象。
堆分类
在这里插入图片描述具体的划分结构:
在这里插入图片描述

我的jdk是1.8的,每台电脑的内存性能不同,只是个近似值。

 VM options :  -Xms1024M -Xmx1024M -XX:+PrintGCDetails
long l = Runtime.getRuntime().totalMemory();
System.out.println("Heap的总内存= "+(l >> 10)+"K  转化成兆= "+(l >> 20)+"Mb");

jvm参数: -XX:+PrintGCDetails
在这里插入图片描述
Heap分配总内存=57344k
PSYoungGen total = 17408K
eden space 15360K,
from space 2048K,
to space 2048K,
ParOldGen total = 39936K
Young + Old ~ Heap 堆内存近似等于 年轻代+老年代 说明元空间并不是堆内存的空间(元空间没有使用堆内存,而是与堆不相连的本地内存区域)。
Young / Old ~ 1 / 2 得知默认情况下老年代的内存空间是新生代的两倍。
from = to 得知新生代的两个存活区(Survivor)一样大,并且在运行期间不停的交换。
eden /(from+to) ~ 8/2 伊甸园比值存活区为8:2。

GC算法

因为堆是存放对象,方法,引用变量,引用常量的。当这些数据长时间不被利用时,就演变成了垃圾,占用堆内存的空间。为了防止内存泄露(OutOfMemoryError)jvm引入了GC垃圾收集
复制算法、标记清除算法、标记压缩算法、引用计数算法。
在这里插入图片描述
eden区和from Survivor区 每次 minor GC 后都会将存活的对象等转移到to Survivor区 。一次minor GC后eden和from Survivor都会清空。在fromSurvivor的对象默认存活了15次GC后就会进入老年代。(存活次数可以调节:-XX:MaxTenuringThreshold=5 来控制进入老年代的时间)
复制算法

  1. 缺点:浪费了内存空间

  2. 最佳使用场景:对象存活度较低的情况

在这里插入图片描述
标记清除法
缺点:两次扫描严重浪费时间,会产生内存碎片。
优点:不需要额外的空间
第一次扫描 标记存活的对象,第二次扫描 GC回收

在这里插入图片描述

总结
内存效率:复制算法 > 标记清除算法 > 标记压缩算法 (时间复杂度)
内存整齐度:复制算法 > 标记压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
没有最好的算法,只有最合适的算法

JVM的GC采用分代收集算法

新生代 采用复制算法,老年代使用 标记清除和标记压缩算法 混合。
垃圾回收是jvm自动完成的工作;即使调用 System.gc() 或Runtime.getRuntime().gc() ,JVM会屏蔽掉显示的垃圾回收调用。

与垃圾回收相关的JVM参数

  • -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
  • -Xmn — 堆中年轻代的大小
  • -XX:-DisableExplicitGC — 让System.gc()不产生任何作用
  • -XX:+PrintGCDetails — 打印GC的细节
  • -XX:+PrintGCDateStamps — 打印GC操作的时间戳
  • -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小
  • -XX:NewRatio — 可以设置老生代和新生代的比例
  • -XX:SurvivorRatio — eden区和Survivor区的比值 8 8:2 Survivor有两个。
  • -XX:PrintTenuringDistribution — 设置每次新生代GC后输出幸存者乐园中对象年龄的分布
  • -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
  • -XX:TargetSurvivorRatio:设置幸存区的目标使用率
JVM的垃圾回收器

Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值