Java内存模型



(图源: 深入理解JVM-内存模型(jmm)和GC)

区域名 英文名 访问权限 作用 备注
程序计数器 Program Counter Register 线程隔离 标记待取的下一条执行的指令 执行Native方法时为空; JVM规范中唯一不会发生OutOfMemoryError的区域
虚拟机栈 VM Stack 线程隔离 每个Java方法执行时创建,用于存储局部变量表,操作栈,动态链接,方法出口等信息 方法执行的内存模型
本地方法栈 Native Method Stack 线程隔离 Native方法执行时使用 JVM规范没有强制规定,如Hotspot将VM和Native两个方法栈合二为一
Java堆 Java Heap 线程共享 存放对象实例 更好的回收内存 vs 更快的分配内存
方法区 Method Area 线程共享 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 JVM规范不强制要求做垃圾收集
运行时常量池 Runtime Constant Pool 线程共享 方法区的一部分
直接内存 Direct Memory - 堆外内存,通过堆的DirectByteBuffer访问 不是运行时数据区的一部分,但也可能OutOfMemoryError

对象的创建——new的时候发生了什么

讨论仅限于普通Java对象,不包括数组和Class对象。

  1. 常量池查找类的常量引用,如果没有先做类加载
  2. 分配内存,视堆内存是否是规整(由垃圾回收器是否具有压缩功能而定)而使用“指针碰撞”或“空闲列表”模式
  3. 内存空间初始化为零值,可能提前在线程创建时分配TLAB时做初始化
  4. 设置必要信息,如对象是哪个类的示例、元信息、GC分代年龄等
  5. 调用<init>方法

垃圾回收器总结

垃圾回收,针对的都是堆。

分代

  • 新生代:适合使用复制算法, 以下三个区一般占比为8:1:1

    • Eden 新对象诞生区
    • From Survivor 上一次GC的幸存者(见“GC种类-minor GC”)
    • To Survivor 本次待存放幸存者的区域
  • 老年代:存活时间较久的,大小较大的对象,因此使用标记-整理或标记-清除算法比较合适
  • 永久代:存放类信息和元数据等不太可能回收的信息。Java8中被元空间(Metaspace)代替,不再使用堆,而是物理内存。

分代的原因

  • 不同代的对象生命周期不同,可以针对性地使用不同的垃圾回收算法
  • 不同代可以分开进行回收

回收算法

名称 工作原理 优点 缺点
标记-清除 对可回收对对象做一轮标记,标记完成后统一回收被标记的对象 易于理解,内存利用率高 效率问题;内存碎片;分配大对象但无空间时提前GC
复制 内存均分两块,只使用其中一块。回收时将这一块存活对象全部复制到另一块 效率高 可用空间减少; 空间不够时需老年代分配担保
标记-整理 对可回收对对象做一轮标记,标记完成后将存活对象统一左移,清理掉边界外内存 内存利用率高 效率问题

标记-X算法适用于老年代,复制算法适用于新生代。

GC种类

  • Minor GC,只回收新生代,将Eden和From Survivor区的存活对象复制到To Survivor
  • Major GC,清理老年代。但因为伴随着新生代的对象生命周期升级到老年代,一般也可认为伴随着FullGC。
  • FullGC,整个堆的回收
  • Mixed GC,G1特有,可能会发生多次回收,可以参考关于G1 GC中Mixed GC的分析

垃圾回收器小结

垃圾回收器名称 特性 目前工作分代 回收算法 可否与Serial配合 可否与ParNew配合 可否与ParallelScavenge配合 可否与SerialOld配合 可否与ParallelOld配合 可否与CMS配合 可否与G1配合
Serial 单线程 新生代 复制 - - - Y N Y N/A
ParNew 多线程 新生代 复制 - - - N N Y N/A
ParallelScavenge 多线程, 更关注吞吐量可调节 新生代 复制 - - - N N Y N/A
SerialOld 单线程 老年代 标记-整理 - - - Y Y N N/A
ParallelOld 多线程 老年代 标记-整理 N N Y - - - N/A
CMS 多线程,并发收集,低停顿。但无法处理浮动垃圾,标记-清除会产生内存碎片较多 老年代 标记-清除 Y Y N Y - - N/A
G1 并行并发收集,追求可预测但回收时间,整体内存模型有所变化 新生代/老年代 整体是标记-整理,局部(两Region)复制 N N N N N N -

在本系列的上一篇文章关于GC(上):Apache的POI组件导致线上频繁FullGC问题排查及处理全过程中,减少FullGC的方式是使用G1代替CMS,计划在下一篇文章中对比CMS和G1的区别。

理解GC日志

只举比较简单的例子,具体各项的格式视情况分析,不同回收器也会有差异。

2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure) 2019-11-22T10:28:32.178+0800: 60188.392: [ParNew: 1750382K->2520K(1922432K), 0.0312604 secs] 1945718K->198045K(4019584K), 0.0315892 secs] [Times: user=0.09 sys=0.01, real=0.03 secs]

开始时间-(方括号[)-发生区域(ParNew,命名和GC回收器有关)-回收前大小-回收后大小-(方括号])-GC前堆已使用容量-GC后堆已使用容量大小-回收时间-使用时间详情(用户态时间-内核时间-墙上时钟时间)

注意这里没有包括“2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure)”这部分的分析。

可借鉴的编程模式

对象分配的并发控制

对象创建是很频繁的,在线程共享的堆中会遇到并发的问题。两种解决办法:

  1. 同步锁定:CAS+失败重试,确保原子性
  2. 堆中预先给每个线程划分一小块内存区域——本地线程分配缓冲(TLAB),TLAB使用完并分配新的TLAB时才做同步锁定。可看作1的优化。

CAS: Conmpare And Swap,用于实现多线程同步的原子指令。 将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。关于CAS可以参考:

Java中的CAS实现原理

CAS系列(3):CAS无锁自旋和同步锁线程切换使用场景对比

对象访问的定位方式

前提条件:通过上本地变量表的reference访问中的对象及它在方法区的对象类型数据(类信息)

主流的两种方式,这两种方式各有优点,可以看出方式2是方式1的优化,但并不是全面超越方式1,无法完全取代。

这里可以看到要权衡垃圾回收和访问速度两方面。

方式1: 直接指针访问实例数据



图源:深入理解JVM-内存模型(jmm)和GC

reference直接存放对象实例地址,只需要一次访问即可,执行效率较高。

方式2: 使用句柄池



图源:深入理解JVM-内存模型(jmm)和GC

reference中地址稳定,对象被移动时只需要改句柄池的地址。相对的,访问实例需要两次指针定位。

参考资料

  1. 周志明.著《深入理解JAVA虚拟机》
  2. 深入理解JVM-内存模型(jmm)和GC
  3. jvm的新生代、老年代、永久代关系
  4. JVM垃圾回收——新生代,老年代,永久代,Minor GC,Full GC

关于GC(中):Java垃圾回收相关基础知识的更多相关文章

  1. JVM垃圾回收的基础知识

    什么是垃圾? 没有任何引用指向的对象,就是垃圾 如何找到垃圾?(2 种方法) 过程:先找到正在使用的对象,然后把没有正在使用的对象进行回收 1.引用数-Reference-Count 被引用数为 0 ...

  2. JAVA面试题相关基础知识

        1.面向对象的特征有哪些方面 ①抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节 ...

  3. java OOP及相关基础知识汇总(转)

    OOP 对象有三个要素 behavior 接口是怎样的,有什么方法/field可以用? state 调用方法的时候,对象会有什么反应? 只有通过调用方法才能改变一个对象的state identity ...

  4. Java GC(垃圾回收)机制知识总结

    目录 Java GC系列 Java关键术语 Java HotSpot 虚拟机 JVM体系结构 Java堆内存 启动Java垃圾回收 Java垃圾回收过程 垃圾回收中实例的终结 对象什么时候符合垃圾回收 ...

  5. [译]Java垃圾回收是如何工作的

    说明:这篇文章来翻译来自于Javapapers 的How Java Garbage Collection Works 这部分教程是为了理解Java垃圾回收的基础以及它是如何工作的.这是垃圾回收系列教程 ...

  6. [译]Java 垃圾回收介绍

    说明:这篇文章来翻译来自于Javapapers 的Java Garbage Collection Introduction 在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的. ...

  7. 细述 Java垃圾回收机制→Types of Java Garbage Collectors

    细述 Java垃圾回收机制→Types of Java Garbage Collectors 转自:https://segmentfault.com/a/1190000006214497 本文非原创, ...

  8. Java基础知识强化83:System类之gc()方法(垃圾回收)以及和finalize()区别

    1. System概述: System类包含一些有用的类字段和方法.它不能被实例化. 2. gc()方法:垃圾回收器 public static void gc()       调用gc方法暗示着Ja ...

  9. Java GC系列(1):Java垃圾回收简介

    本文由 ImportNew - 好好先生 翻译自 javapapers. Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Ja ...

随机推荐

  1. c使用二叉链表创建二叉树遇到的一些疑问和思考

    二叉链表存储二叉树 学习的时候参考的是<大话数据结构>,书中是这样定义的 typedef char TElemType; typedef struct BiTNode { TElemTyp ...

  2. 在Hadoop上用Python实现WordCount

    一.简单说明 本例中我们用Python写一个简单的运行在Hadoop上的MapReduce程序,即WordCount(读取文本文件并统计单词的词频).这里我们将要输入的单词文本input.txt和Py ...

  3. 如何在项目中使用Spring异步调用注解@Async

    本文主要介绍如何使用Spring框架提供的异步调用注解@Async,异步线程池配置.异常捕获处理. 开启@Async注解支持 使用@Async注解的之前,必须在项目中启动时调用@EnableAsync ...

  4. MySQL注入--Payload

    MySQL注入--Payload Mirror王宇阳 2019-10-22 SQL的注入流程一般如下: 1.判断是否有SQL注入漏洞(判断注入点) 2.判断数据库的系统架构.数据库名.web应用类型等 ...

  5. 程序员修神之路--为什么有了SOA,我们还用微服务?

    菜菜哥,我最近需要做一个项目,老大让我用微服务的方式来做 那挺好呀,微服务现在的确很流行 我以前在别的公司都是以SOA的方式,SOA也是面向服务的方式呀 的确,微服务和SOA有相同之处 面向服务的架构 ...

  6. CodeForces - 1214D B2. Books Exchange (hard version)

    题目链接:http://codeforces.com/problemset/problem/1249/B2 思路:用并查集模拟链表,把关系串联起来,如果成环,则满足题意.之后再用并查集合并一个链,一个 ...

  7. [AHOI2002]哈利·波特与魔法石

    这道题比较简单,就是一个最短路(SSSP).数据水,用Floyd即可AC.这里用了Dijkstra. #include <iostream> #include <cstdio> ...

  8. 使用JRebel插件实现SpringBoot应用代码热加载

    前言 在实际的开发过程中,我们经常修改代码之后,手动的重启项目,查看修改效果.那么有没有一种方式能够快速的.自动的帮我们将修改代码自动更新,避免手动重启,从而提高开发效率呢?是有的,在我之前的文章里面 ...

  9. config.xml

    ASP.NET应用程序的配置信息都存放于Web.config配置文件中,Web.config配置文件是基于XML格式的文件类型,由于XML文件的可伸缩性,使得ASP.NET应用配置变得灵活.高效.容易 ...

  10. Shiro learning - 入门案例(2)

    Shiro小案例 在上篇Shiro入门学习中说到了Shiro可以完成认证,授权等流程.在学习认证流程之前,我们应该先入门一个Shiro小案例. 创建一个java maven项目 <?xml ve ...