Java JVM再理解

版权声明:翀版 https://blog.csdn.net/biggerchong/article/details/83153317

JVM简介:

Java语言一个非常重要的特点平台无关性主要是通过使用Java虚拟机(JVM)来实现。JVM是指运用硬件或软件手段实现虚拟的计算机。Java中使用JVM实现跨平台的原理如下图: 


                                                 

垃圾回收:(Garbage Collection——GC)

Java系统提供了垃圾收集器,用于自动检查每一快分配出去的内存空间,然后将无价值的内存快自动回收。(对象不再被应用时自动进行垃圾回收

在Java语言中,判断内存空间是否符合垃圾收集器的收集标准有2个:

  1. 为对象赋予了空值NULL后再没有调用过
  2. 为对象赋予了新值,即重新分配了内存空间

因为垃圾回收机制的存在,使得Java程序中不存在delete函数、析构函数

(注:虽说Java中有GC的存在,但是也不能随意的new太多对象,GC也是需要时间来回收的,new太多会导致来不及GC。)


(附加)JVM再理解

(1)JVM基本结构说明

  1. 类加载子系统与方法区:类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中可能还会存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
  2. java堆: java堆在虚拟机启动的时候建立,它是java程序最主要的内存工作区域。几乎所有的java对象实例都存放在java堆中。堆空间是所有线程共享的,这是一块与java应用密切相关的内存空间。
  3. 直接内存: java的NIO库允许java程序使用直接内存。直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于jawa 堆。因此出于性能的考虑,读写频繁的场合可能会考态使用直接内存。由于直接内存在java堆外,因此它的大小不会直接受限于Xmx指定的最大堆大小,但是系统内存是有限的,java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
  4. 垃圾回收系统:垃圾回收系统是java虚拟机的重要组成部分,垃圾回收器可以对方法区、java堆和直接内存进行回收。其中,java堆是垃圾收集器的工作重点。和C/C++不同,java中所有的对象空间释放都是隐式的,也就是说,java中没有类似free()或者delete()这样的函数释放指定的内存区域。对于不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括java堆、方法区和直接内存中的全自动化管理。
  5. java:毎一个java虚拟机线程都有-个私有的java桟, -个线程的java桟在线程创建的吋候被創建, Java桟中保存着幀信息,java桟中保存着局部変量、方法鯵数,同吋和java方法的调用、返回密切相关。
  6. 本地方法桟:本地方法桟和java 桟非常类似,最大的不同在于java桟用于方法的调用,而本地方法桟则用于本地方法的调用,作为対java虚拟机的重要拓展, java虚拟机允許java直接調用本地方法(通常使用C編写)
  7. PC〈Program Counter): PC寄存器也是毎个銭程私有的空间,java虚拟机会为毎一个java銭程創建pc寄存器.在任意肘刻,-个java銭程总是在执行一个方法,这个正在被执行的方法称为当前方法.如果当前方法不是本地方法, PC寄存器就会指向当前正在被执行的指令。如果当前方法是本地方法,那么PC寄存器的値就是undefined
  8. 执行引擎:执行引撃是java虚拟机的最核心組件之一,它负责执行虚拟机的字节碼,現代虚拟机为了提高执行效率,会使用即时编译将方法编译成机器码再执行。

Java HotSpot Client VM(-Client), 为在客户端环境下减小启动时间而优化

Java HotSpot Server VM(-server)为在服务器环境下最大化程序执行速度而设计

 

(2)JVM堆结构图及分代

Java虚拟机: JVM内存分代策略

Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代、老年代和永久代(对HotSpot虚拟机而言),这就是JMM的内存分代策略。

为什么要分代?

 堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中.给堆内存分代是为了提高对象内存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率,这简直太可怕了。有了内存分代,情况就不同了,新创建的对象会在新生代中分配内存,经过多次回收仍然存活下来的对象存放在老年代中,静态属性、类信息等存放在永久代中,新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC,老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收,永久代中回收效果太差,一般不进行垃圾回收,还可以根据不同年代的特点采用合适的垃圾收集算法。分代收集大大提升了收集效率,这些都是内存分代带来的好处。

 内存分代划分

  Java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念它采用永久代的方式来实现方法区,其他的虚拟机实现没有这一概念而且HotSpot也有取消永久代的趋势,在JDK 1.7中HotSpot已经开始了“去永久化”,把原本放在永久代的字符串常量池移出。永久代主要存放常量、类信息、静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。内存分代示意图如下:

新生代(Young Generation)

  新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70%~ 95%的空间,回收效率很高。

  HolSpot将新生代划分为三块,一块较大的 Eden空间和两块较小的Survivor空间,默认比例为8: 1: 1。划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一-次 MinorGC。GC开始时,对象只会存在于Eden区和From Survvor区,To Survivor区是空的(作为保留区域)。GC进行时,Eden区中所有存活的对象都会被复制到To Survvor区,而在FromSurvvor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存诸在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接看清空Eden区和From Surwvor区,新生代中存活的对象都在To Survivor区。接着, From Survvor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的FromSurvivor区,新的From Survivor区就是上次GC的To Survlvor区,总之,不管怎样都会保证To Suvvor区在一轮GC后是空的。GC时当To Survivar区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。

老年代(Old Generationn)

在新生代中经历了多次<具体看虚拟机配置的阀值》GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。

永久代(Permanent Generationn)

永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。

(3)JVM垃圾回收算法及收集器

1)垃圾回收常见算法

引用计数( Reference Counting):

比较古老的回收算法。原理是此对象有一个引用则增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只收集计数为0的对象。此算法最致命的是无法处理循环引用的

复制( Copying):

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。如下图:

 

 

 

标记-清除(Mark-Sweep):

此算法执行分两个阶段,第一阶段从应用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清楚。此算法需要暂停整个应用,同时会产生内存碎片。如下图:

 

标记-整理( Mark-Compact ) :

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第-阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除末标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除” 的碎片问题,同时也避免了“复制”算法的空问问题。如下图:

(4)JVM中垃圾收集器

ScavengeGC (次收集)和Full GC的区别(全收集)

新生代GC (Scavenge GC) : Scavenge GC指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死;所以Scavenge GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Scavenge GC。一般情况下 ,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出

老年代GC (Full GC/MajorGC) : Full GC指发生在老年代的GC,出现了FullGC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度-般会比MinorGC慢10倍以上。当老年代内存不足或者显式调用System.gc( )方法时,会触发Full GC。

次收集   当年轻代堆空间紧张时会被触发

相对于全收集而言,收集问隔较短

全收集    当老年代或者持久代堆空间满了,会触发全收集操作

可以使用System.gc( )方法来显式的启动全收集

  全收集-般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。不过,如果全收集时间超过3到5秒钟,那就太长了.

 

(5)分代垃圾回收器

年轻代

串行收集器(Serial):

并行收集器(ParNew):

Parallel Scavenge收集器:

  与ParNew类似Parallel Scavenge也是使用复制算法,也是并行多线程收集器.但与其他收集器关注尽可能缩短垃圾收集时间不同,Parallel Scavenge更关注系统吞吐量系统吞吐量=运行用户代码时间运行用户代码时间+垃圾收集时间)停顿时间越短就越适用于用户交互的程序良好的响应速度能提升用户的体验:而高吞吐量则适用于后台运算而不需要太多交互的任务可以最高效率地利用CPU时间,尽快地完成程序的运算任务. ParallelScavenge提供了如下参数设置系统吞吐量:

 

老年代

Serial Old收集器:

Paraller Old收集器:

CMS收集器:

分区收集—G1收集器

 

分代垃圾回收总览图

 

(谢谢阅读,欢迎评论)

猜你喜欢

转载自blog.csdn.net/biggerchong/article/details/83153317