JVM

JVM–双亲委派机制


通俗的讲,虚拟机是根据类的全限定名来加载类的,那么有个问题,如果同时存在两个或多个全限定名完全一致的情况下。该如何选择加载哪个类。这就是双亲委派机制要做的工作。

在这里强加个知识点:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有真正的意义,否则,即使这两个类来源于同一个class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。

类装载方式,有两种 :

  • 隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
  • 显式装载, 通过class.forname()等方法,显式加载需要的类

回到双亲委派的问题上,接下来了解下类加载器的种类:

1-启动类加载器,负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径;

2-扩展类加载器:负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径;

3-应用程序类加载器:负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器。

img

双亲委派机制得工作过程:

1-类加载器收到类加载的请求;

2-把这个请求委托给父加载器去完成,一直向上委托,直到启动类加载器;

3-启动器加载器检查能不能加载(使用findClass()方法),能就加载(结束);否则,抛出异常,通知子加载器进行加载。

4-重复步骤三;

以上就是双亲委派机制的原理。

JVM内存

img

img

img

Java 栈(Stack)

1、Java 栈是与每一个线程关联的,JVM 在创建每一个线程的时候,会分配一定的栈空间给线程,Java Stack 为每个线程独享。

2、Java Stack 主要用来存储线程执行过程中的局部变量方法的返回值,以及方法调用上下文(对象的引用), 以帧为单位保存线程的运行状态。

3、栈空间随着线程的终止而释放。

4、StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么 JVM 就会抛出此异常,这种情况一般是死递归造成的。

堆(Heap)

1、Java 中堆是由所有的线程共享的一块内存区域,堆用来保存各种 JAVA 对象,比如数组,线程对象等。

2、JVM 堆一般又可以分为以下三部分:

img

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

1、新生代(Young Generation)

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

2、老年代(Old Generationn)

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

3、永久代(Permanent Generationn)

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

堆 VS 栈

栈:存放变量引用的地方,以及基本数据类型

堆:存放实际对象的地方,即数组、线程对象等。

垃圾回收算法

**1、标记-复制:**它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。

JVM实现原理:Survivor区,一块叫From,一块叫To,对象存在Eden和From块。当进行GC时,Eden存活的对象全移到To块,而From中,存活的对象按年龄值确定去向,当达到一定值(年龄阈值,通过-XX:MaxTenuringThreshold可设置,默认=15)的对象会移到年老代中,没有达到值的复制到To区,然后直接清空Eden和From。之后,From和To交换角色,新的From即为原来的To块,新的To块即为原来的From块,且新的Form块中对象年龄加1.
优点:内存分配时也不用考虑内存碎片等问题;实现简单,运行高效;可以利用指针碰撞(bump-the-pointer)实现快速内存分配
缺点:

  • 空间浪费:可用内存缩减为原来的一半,太过浪费(解决:可以改良,不按1:1比例划分);
  • 效率随对象存活率升高而变低:当对象存活率较高时,需要进行较多复制操作(对象的引用地址需要复制),效率将会变低,所以该算法不适合对象存活率较高的场景或者区域。

应用场景:

  • 现在商业JVM都采用这种算法(通过改良缺点1)来回收新生代;
  • 如Serial收集器、ParNew收集器、Parallel Scavenge收集器、G1(从局部看)。

**2、标记-清除:**首先标记出需要回收的对象,标记完成之后统一清除对象。

标记:从根集合开始扫描,标记存货的对象
清除:扫描整个堆内存空间,回收未被标记的对象,使用free-list记录可以使用的区域

优点:基于最基础的可达性分析算法,它是最基础的收集算法;而后续的收集算法都是基于这种思路并对其不足进行改进得到的;
缺点:

  • 效率问题:标记和清除都需要扫描,两个过程的效率都不高;
  • 空间问题:标记清除后会产生大量不连续的内存碎片,这会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次垃圾收集动作。
  • stop-the-Word:在标记时需要暂停JVM用户进程

应用场景:针对老年代

3、标记-整理

img

标记-整理:标记操作和“标记-清理”算法一致,后续操作不只是直接清理对象,而是在清理无用对象前,先将存活的对象都向一端移动,并更新引用其对象的指针,然后直接清理掉端边界以外的内存。

标记:和“标记-清理”算法一致

整理:扫描整个堆内存空间,将存活的对象都向一端移动,并更新引用其对象的指针,然后直接清理掉边界以外的内存。整理的目的就是整合零散分布的空间碎片为一个连续的空间。

优点:

  • 不会像复制算法,效率随对象存活率升高而变低
  • 不会像标记-清除算法,产生内存碎片,因为清除前,进行了整理,存活对象都集中到空间一侧;

缺点:主要是效率问题:除像标记-清除算法的标记过程外,还多了需要整理的过程,效率更低;
应用场景:回收老年代;
4、标记-清除-整理(Mark-Sweep-Compact)
  该算法是标记清除和标记整理的结合,标记-清除会产生碎片,标记-整理每次都进行整理效率不高;标记-清楚-整理 是如果老年代内存中没有一块连续续的空间可以存放将要进入对象,就进行整理;如果内存中的空间可以存放将要进入的对象,就进行标记-清除,这样就节省了整理的步骤可以提高效率。总结一句话:不是所有的时候都需要整理的,因为整理也付出代价。主要应用于老年代

总结: 没有最好的算法,只有最合适的引用场景

img

入的对象,就进行标记-清除,这样就节省了整理的步骤可以提高效率。总结一句话:不是所有的时候都需要整理的,因为整理也付出代价。主要应用于老年代

总结: 没有最好的算法,只有最合适的引用场景

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值