看完这篇文章,再也不怕别人问我JVM了

一、JVM

       JVM(JavaVirtualMachine)Java虚拟机的简称,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。一般的高级语言如果要在不同的平台上运行,至少要编译成不同的目标代码,而引入JVM后,Java语言在不同平台上运行时不需要重新编译,因为JVM屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

二、JVM内存结构

       Java源代码编译成JavaClass文件后通过类加载器ClassLoader加载到JVM中。
在这里插入图片描述
       1、方法区:属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。

       2、堆:主要是存放对象的实例和数组。内部会分配出多个线程私有的缓冲区(ThreadLocalAllocationBuffer,TLAB)。可以位于物理地址不连续的空间,但是逻辑上要连续。

       3、虚拟机栈:每个方法在执行时都会创建一个栈帧(StackFrame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

       4、程序计数器:分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

       5、本地方法栈:本地方法栈不同于Java虚拟机栈,后者为虚拟机执行Java方法(也就是字节码),而前者则为虚拟机使用的Native方法提供服务。当然,二者也都会出现StackOverflowError和OutOfMemoryError异常。

       6、解释器:方法执行时每行代码由解释器逐行执行。

       7、JIT编译器:热点代码由JIT编译器即时编译。

       8、垃圾回收:回收堆中资源。

       9、本地方法接口:和操作系统打交道。

三、内存模型

       Java内存模型简称JMM(JavaMemoryModel),它能屏蔽掉各种硬件和操作系统的内存访问差异,以此实现Java程序在各种平台下一致的内存访问效果,不必因为不同平台上的物理机的内存模型的差异,对各平台定制化开发。
在这里插入图片描述
       1、堆:对于大多数应用来说,Java堆(JavaHeap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

       2、方法区:方法区(MethodArea)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做非堆(Non-Heap),目的应该是与Java堆区分开。
       垃圾收集行为在这个区域是比较少出现的,其内存回收目标主要是针对常量池的回收和对类型的卸载。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

       3、虚拟机栈:虚拟机栈(JavaVirtualMachineStacks)是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(StackFrame),它是方法运行时的基础数据结构,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

       4、本地方法栈:本地方法栈(NativeMethodStack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务(SunHotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一)。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
       线程开始调用本地方法时,JVM暂时无法对其约束。而本地方法可以通过JNI(JavaNativeInterface)来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和JVM相同的能力和权限。当大量本地方法出现时,势必会削弱JVM对系统的控制力,因为它的出错信息都比较黑盒。如果出现内存不足的情况,本地方法栈还是会抛出NativeheapOutOfMemory。

       5、程序计数器:程序计数器(ProgramCounterRegister)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

四、垃圾回收

       Java相比C/C++最显著的特点便是引入了自动垃圾回收(下文统一用GC指代自动垃圾回收),它解决了C/C++最令人头疼的内存管理问题,让程序员专注于程序本身,不用关心内存回收这些恼人的问题。

1、垃圾识别机制

1.1引用计数法

       简单地说,就是对象被引用一次,在它的对象头上加一次引用次数,如果没有被引用(引用次数为0),则此对象可回收。但是缺点是无法解决对象之间相互循环引用的问题,如下图:虽然a,b都被置为null了,但是由于之前它们指向的对象互相指向了对方(引用计数都为1),所以无法回收,也正是由于无法解决循环引用的问题,所以现代虚拟机都不用引用计数法来判断对象是否应该被回收。
在这里插入图片描述
1.2可达性算法

       通过一系列的称为"GCRoots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(ReferenceChain),当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的。此算法解决了上述循环引用的问题。
在这里插入图片描述
       而图中a,b对象虽然是可回收的,但是会首先判断对象是否执行finalize方法,如果未执行,则会先执行finalize方法,我们可以在此方法里将当前对象与GCRoots关联,这样执行finalize方法之后,GC会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收。

       这里finalize方法只会被执行一次,如果第一次执行finalize方法此对象变成了可达之后,确实不会回收。但如果对象再次被GC,则会忽略finalize方法,对象会被回收。

       这里补充一下强引用、软引用、弱引用、虚引用。JDK1.2以前,一个对象只有被引用和没有被引用两种状态。后来,Java对引用的概念进行了扩充,将引用分为强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)、虚引用(PhantomReference)4种,这4种引用强度依次逐渐减弱。

       强引用:指在程序代码之中普遍存在的,类似"Objectobj=new Object()"这类的引用,垃圾收集器永远不会回收存活的强引用对象。

       软引用:有用但非必需的对象。在系统将要发生内存溢出异常之前 ,将会把这些对象列进回收范围之中进行第二次回收。

       弱引用:被弱引用关联的对象 只能生存到下一次垃圾收集发生之前 。当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。

       虚引用:虚引用是最弱的一种引用关系。无法通过虚引用来取得一个对象实例 。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

2、垃圾回收算法

       主要有四种算法:标记-清除算法、复制算法、标记整理算法、分代收集算法。

       2.1标记-清除算法

       首先根据可达性算法标记出相应的可回收对象(图中黄色部分),然后对可回收的对象进行回收。
在这里插入图片描述
缺点:
       效率问题:标记和清除两个过程的效率都不高;

       空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

       2.2复制算法

       把堆等分成两块区域A和B,区域A负责分配对象,区域B不分配,对区域A使用以上所说的标记法把存活的对象标记出来,然后把区域A中存活的对象都复制到区域B(存活对象都依次紧邻排列)最后把A区对象全部清理掉释放出空间,这样就解决了内存碎片的问题。
在这里插入图片描述
       缺点:不合理分配,效率很低。比如分配给堆500M内存,复制完成后只用了250M,空间平白无故少了一半,这是不合理的。而且每次回收也要把存活对象移动到另一半,效率很低。

       2.3标记整理算法

       前面两步和标记清除法一样,不同的是它在标记清除法的基础上添加了一个整理的过程,即将所有的存活对象都往一端移动,紧邻排列(如图示),再清理掉另一端的所有区域,这样的话就解决了内存碎片的问题。
在这里插入图片描述
       缺点:每进一次垃圾清除都要频繁地移动存活的对象,效率很低。

       2.4分代收集算法

       先来介绍一下三个关键词:新生代、老生代、永久代。

       2.4.1新生代:主要是用来存放新生的对象。一般占据堆空间的1/3,由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代分为Eden、ServivorFrom、ServivorTo三个区。

       Eden区:Java新对象的出生地(如果新创建的对象占用内存很大则直接分配给老年代)。当Eden区内存不够的时候就会触发一次MinorGC,对新生代区进行一次垃圾回收。
       ServivorTo:保留了一次MinorGC过程中的幸存者。

       ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。当JVM无法为新建对象分配内存空间的时候(Eden区满的时候),JVM触发MinorGC。因此新生代空间占用越低,MinorGC越频繁。

       2.4.2老生代:老生代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC之前,一般都先进行了一次MinorGC,使得有新生代的对象进入老生代,当老生代空间不足时就会触发MajorGC。

       2.4.3永久代:指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。

       当前商业虚拟机的垃圾收集器都采用“分代收集”(GenerationalCollection)算法,根据对象存活周期的不同将内存划分为几块并采用不用的垃圾收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老生代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清除”或者“标记—整理”算法来进行回收。而永久代对象会被一直保存。

五、垃圾回收触发条件

       1、System.gc()方法的调用;

       2、老生代空间不足;

       3、方法区空间不足;

       4、通过MinorGC后进入老生代的平均大小大于老生代的可用内存;

       5、由Eden区、FromSpace区向ToSpace区复制时,对象大小大于ToSpace可用内存,则把该对象转存到老生代,且老生代的可用内存小于该对象大小。


xDroid——让安卓应用运行在Linux平台上

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值