虚拟机JVM运行时区域及垃圾回收

该系列文章主要是记录下自己暑假这段时间的学习笔记,暑期也在实习,抽空学了很多,每个方面的知识我都会另起一篇博客去记录,每篇头部主要是另起博客的链接。

JavaSE集合(已写)

JavaEE框架(未写)

虚拟机JVM运行时区域及垃圾回收(已写)

Java并发(未写)

计算机网络(已写)

八大经典排序算法原理及实现(已写)


一、JVM 运行时数据区域

JVM运行时数据区域可以分为:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区(包含运行时常量池)

1、程序计数器:

可看做是当前线程所执行的字节码的行号指示器

  • 线程私有

2、Java 虚拟机栈

描述的是Java方法执行的的内存模型,每一个方法执行的同时都会创建一个栈帧栈帧详解)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行到结束,都对应着一个栈帧入栈到出栈

  • 局部表量表:存放了编译器可知的各种基本数据类型、对象引用
  • 线程私有
  • 如果线程请求的栈深度大于虚拟机所允许的深度会抛出StackOverflowError异常
  • 如果虚拟机栈动态扩展时无法申请到足够的内存,会抛出OutOfMemoryError错误

3、本地方法栈

与Java虚拟机栈的发挥的作用差不多,区别在于Java虚拟机栈是为Java方法服务、本地方法栈是为本地(Native)方法服务

  • 线程私有
  • 如果线程请求的栈深度大于虚拟机所允许的深度会抛出StackOverflowError异常
  • 如果虚拟机栈动态扩展时无法申请到足够的内存,会抛出OutOfMemoryError错误

4、Java 堆

堆在虚拟机启动时创建,唯一的目的就是存放几乎所有的对象实例。

  • 线程共享
  • 堆是垃圾收集的主要区域,常被称为GC堆(Garbage Collected Heap)
  • 可分为新生代、老年代
  • 再细致可以分为Eden、From Survivor、To Survivor空间
  • 堆中没有内存完成实例分配,并且堆也无法扩展时,抛出OutOfMemoryError

5、方法区

存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据

  • 线程共享
  • 当方法区无法满足内存分配需求时,将抛出OutOfMemooryError

6、运行时常量池

是方法区的一部分.Class文件除了有类的版本、字段、方法、接口等描述信息外;还有一项常量池,用于存放编译期生成的各种字面量和符号引用??,这些内容再类被加载后进入方法区的运行常量池中存放。

  • 运行时常量池相对于Class文件常量池具有动态性,Java语言不要求常量一定只有编译期才产生,String类的intern()方法可以在运行期间将常量插入运行时常量池
  • 当常量池无法再申请到内存时,会抛出OutOfMemoryError

7、直接内存

并不是虚拟机运行时数据的一部分,也不是Java虚拟机规范中定义的内存区域,但是频繁的使用也会造成OutOfMemooryError

二、对象的创建

大家都知道Java是一门面向对象的编程语言,在Java程序运行过程中无时无刻都有对象被创建。语言层面上有通过 new方式 来创建、也有通过反射机制方式创建。在JVM中是怎么创建的呢?

  1. 类加载检查:当创建对象指令下达后,先检查是否能在常量池中定位到该类的符号引用,并且检查这个类是否已经被加载、解析、和初始化过。若没有,那必须先执行类加载
  2. 为新生对象分配内存:对象所需分配的大小在类加载完成后便可完全确定,分配内存其相当于在堆中划分出一块相应的大小的内存。在为对象分配内存时,需要考虑线程安全的问题
  3. 将分配到的内存空间都初始化为0值(不包括对象头):保证了对象的实例字段在Java代码中可以不赋初值就直接使用
  4. 对对象进行必要的设置:比如对象是哪个类的实例,如何才能找到元数据信息,对象个哈希码、对象的GC分代信息等。这些信息都存储在对象头中
  5. 上述步骤之后,虚拟机层面上一个新对象已经产生,但从Java层面上对象的创建才开始。其 <init>方法还没有执行,所有的字段都还未0

三、对象的访问定位

建立对象都是为了使用它,在Java程序中,通过栈中的 reference 数据来操作堆上的具体对象

  • 句柄访问方式

  • 直接指针访问方式

四、JVM垃圾收集器与内存分配策略

在了解JVM垃圾回收之前,需要知道3个问题:

  1. 哪些内存需要释放?
  2. 什么时候释放?
  3. 如何回收?

首先要知道JVM运行时数据区域中程序计数器、Java虚拟机栈、本地方法都是随线程而生、线程而亡,即线程私有的。在这3个区域的内存分配和回收都具有确定性,在方法结束或线程结束时内存就自动回收了,因此不用考虑垃圾回收。
在Java堆中和方法区则不一样,一个接口的多个实现类需要的内存不一样,一个方法的多个分支需要的内存也不一样,只有在程序运行期间才能知道,这部分内存的分配和回收都是动态的。因此在这两个区域考虑垃圾回收。

判断对象是否存活

1.引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效后,计数器就减1;当计数器为0时的对象就是不可能再被使用的对象

  • 优势:实现简单、效率高
  • 劣势:难以解决对象之间相互循环引用的问题

2、可达性分析算法

通过一系列称为"GC Roots"对象作为起始点,从这些点向下搜索,搜索过程中走过的路径称为引用链,当一个对象到GC Roots没有任何的引用链相连接时,则证明该对象是不可用的

  • 克服引用计数算法无法解决对象之间相互循环引用的问题
  • GC Root 的对象包括:
    1. 虚拟机栈中引用的对象
    2. 方法区中类静态属性引用的对象
    3. 方法区中常量引用的对象
    4. 本地方法栈中JNI(Native方法)引用的对象

3、再次确认对象的存亡

经过上述任意一种算法,判断出不可用的对象并非是非死不可的。一个不可用的对象再第经过上述算法之后,将第一次标记并且进行筛选,筛选条件为该对象是否有必要执行finelize()方法。
当对象没有覆盖finelize()方法,或者finelize()方法已经被虚拟机调用过,JVM将这两种情况都视为没有必要执行

4、回收方法区

很多人认为JVM方法区(永久代)中是没有垃圾收集的,其实是有的,主要回收这两部分:废弃常量和无用的类

垃圾回收算法

1、标记-清除算法

如同其名字一样,算法分为标记清除两个阶段:首先标记所有需要回收的对象,
在统一回被标记的对象;其标记过程和判断对象的存活过程类似

  • 不足:
    1. 标记和清除两个过程效率都低;
    2. 标记清除后会产生大量不连续的内存碎片,空间碎片太多会导致后面分配大内存对象时,无法找到足够的连续内存而不得不提前出发一个垃圾收集动作

2、复制算法

将可用内存划分为大小相等的两块,每次只使用其中的一块;当这一块内存快使用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的那块内存空间一次清理掉。

  • 解决了标记清理的效率和内存碎片问题
  • 不足:将内存缩小为原来的一半,付出的代价太高
新生代中解决复制算法的不足:

研究表明新生代中的对象98%是“朝生夕死”的,所以并不需要按1:1比例来划分空间,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,其比例为8:1:1,每次使用Eden空间和一块Survivor空间,即新生代中可用内存空间为90%。
若另一块Survivor空间没有足够空间存放上一次新生代中收集下来存活的对象,这些对象将会直接被分配到老年代

3、标记整理算法

  • 复制算法在对象存活率较高时就要进行较多复制操作,效率也会变低,更关键是会浪费空间

针对老年代的特点,提出标记-整理算法,其标记过程和标记清除算法中一样,而后续步骤则不一样,而是让所有存活的对象都向一端移动,然后直接清除掉该端边界以外的内存。

  • 标记-整理主要解决了内存碎片问题

分代收集算法

将JVM堆分为新生代老年代,这样可以根据各个年代的特点采用最合适的收集算法

  • 新生代中,每次垃圾收集时发现大批对象死去,只有少量对象存活,就选用复制算法,只需付出少量存活对象的复制成本就可以完成收集

  • 老年代中,对象的存活率很高,无额为的空间为其进行分配担保,就必须使用标记-清除标记-整理算法

简单总结:

  • 两个最基本的java回收算法:复制算法和标记清理算法
    • 复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
    • 标记清理:一块区域,标记要回收的对象,然后回收,一定会出现碎片,那么引出
    • 标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
  • 两个概念:新生代和年老代
    • 新生代:初始对象,生命周期短的
    • 永久代:长时间存在的对象
  • 整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收

垃圾收集器

  • **Serial **收集器是针对新生代的收集器,采用的是复制算法

  • Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理

  • Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法

  • Serial Old(串行)收集器,新生代采用复制,老年代采用标记清理

  • Parallel Old(并行)收集器,针对老年代,标记整理

  • CMS收集器,针对老年代,基于标记清理

  • G1收集器,整体上是基于标记清理,局部采用复制

  • 综上:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理。

JVM内存分配参数

  • Xmx:最大堆大小
  • Xms:初始堆大小,即最小内存值
  • Xmn:年轻带大小
  • XXSurvivorRatio:年轻带中 Eden:FromSurvivor:ToSurvivor=3:1:1
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容