本文章是我参考多篇技术文章整理后自己总结出来的内容
前言
很多人喜欢拿java和c做比较,来判定哪一种语言更好,其实我觉得c和java应该是属于两个完全不同的领域,c更倾向于硬件,效率,而java更加倾向于业务,安全并发等等,java有很强的移植性,可以不顾及底层操作系统以及各种驱动的环境,具有一次编译遍地运行的特点,c更倾向于发挥机器的性能
什么是java虚拟机 (JVM)?
jvm全称-java Virtual Machine 翻译过来也就是java虚拟机的意思,大家都知道java是一门跨平台性很强的语言,而铸造java这一特性的最大功臣便是java的jvm,java程序的整体执行流程如下
从图上可以看出来,java的执行并不是直接编译成二进制而是变成成字节码文件,什么是字节码呢?我们可以理解为只有jvm能理解的运行的编码,这便是和C语言最大的不同之处,java编译后的字节码文件必须要依赖jvm虚拟机进行解释才能运行,这也就告诉我们了为什么java具有那么好的跨平台的能力,所以也可以这样理解jvm基本上是一切java程序的基石
什么是垃圾回收机制?
大家都知道程序运行的时候是需要使用内存的,而内存又是有限的宝贵资源,程序在执行完代码片段后,需要释放不在需要的内存空间,腾出空间给后续的代码运行,传统C等语言都是程序员自行分配释放资源的,比如某一个代码片段程序员需要先调用构造函数分配内存给对象,在业务代码执行完后再调用析构函数去释放这个对象。java则把这个任务交个java虚拟机JVM去执行,不需要人物操作
从中可以看出java里面GC的线程全程跟踪着代码的运行,并自身能触发垃圾回收,GC由jvm提供,是一种守护线程,负责监控并触发垃圾回收GC全程异步执行
JVM的内存结构
为了更好的了解java的程序运行以及java垃圾回收机制,我们先来看看jvm的内存模型是怎么样的
从图中可以看出,jvm里面最大的一块区域当属与运行时数据区(Runtime Data Area),基本上程序代码的数据都将会保存到这个区域中去,其中这块区域的结构:
从图中看出,运行时数据区在运行的时候被拆分成多个区域,每个区域都有特定的任务
-
方法区(Method Area):
方法区属于线程共享的内存区域,主要用于存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool)的区域,它主要用于存放编译器生成的各种字面量和符号引用,比如:Integer a = 100 (-127-128范围的数据)或者String a ="abc" 等等,这些内容将在类加载后存放到运行时常量池中,以便后续使用。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。
-
堆(Java Heap):
Java 堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,注意Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做GC 堆,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。
-
程序计数器(Program Counter Register):
属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
-
虚拟机栈(Java Virtual Machine Stacks):
属于线程私有的数据区域,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。每个方法从调用直结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程,程序中方法声明的时候都会保存在这里。从下图中也可以看出,如果一个程序不停的调用递归会导致这个区域的栈溢出*Error
-
本地方法栈(Native Method Stacks):
本地方法栈属于线程私有的数据区域,这部分主要与虚拟机用到的 Native 方法相关,可以忽略这块区域。
结尾(对象的存活周期)
基本上所有创建出来的对象都是保存在堆里面的,垃圾回收的时候,会把特定不必要的对象释放,也因此有一些特定的对象不需要释放的需要和被释放的区分开,所以在堆区里面对象按照特定的方式来划分
对象按照存活周期主要分为三大类:年轻代,老年代,持久代
持久代类型主要存放的是Java类的基本信息,基本上不会涉及到垃圾回收。年轻代和年老代的是对垃圾回收机制的重点对象。年轻代中又划分了多个区分别是:Eden区、两个Survivor区。创建的对象分配是首先放在Eden区、Survivor区作为Eden区和老区的缓冲当垃圾回收技术存活下来的Eden区会放入Survivor区,在Survivor区的对象经历若干次收集任然存活的,就会被转移到年老区。
年轻代:
所有新生成的对象首先都是放在年轻代的。年轻代大部分都是生命周期比较短的对象。年轻代里面有三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时触发垃圾回收,还存活的对象将被复制到Survivor区,当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
持久代:
用于存放静态文件,如今Java类、方法等。基本上垃圾回收不会参与到,不影响垃圾回收