都说搞C的牛叉,那是因为C解决问题,全靠程序员自己,他们对自己的程序在内存中是什么样了如指掌。而Java呢不需要有太多操作系统的知识,不用时刻注意内存的问题,但这不代表我们就不用去了解它背后的原理。Java之所以容易上手,那是因为最困难的问题,已经被前人解决了,而这一切都归功于Java Virtual Machine-Java虚拟机,JVM其实就是一个抽象的计算机,它有自己的指令集,有自己的机器语言,有自己的内存管理。本系列会一一解开它的真面目。

  本文基于Java HotSpot™ 虚拟机,JDK 1.8,将讨论:

  • JVM 内部结构
  • JVM 内存管理
  • JVM 内存模型

1. JVM 内部结构

  JVM是用C/C++ 编写的一个软件,在Linux的体现就是一个进程(进程是操作系统的一个重要抽象CPU,内存和IO)。在了解JVM内部结构之前,我们先看看Linux中进程虚拟地址空间的结构:

图 1 进程地址空间结构

  JVM内部虽然与它不同,但简单了解一下,对后面的理解还是用好处的,下面从下往上简单的介绍一下:

(1)data & code

  这里主要存储编译后的一些文件,主要是未初始化和已初始化的全局变量,静态数据如字符串常量和可执行程序的二进制代码。

(2)heap area

  堆,动态内存分配,在运行时,由程序员手动申请和释放,如malloc,free。堆的大小向上增长,内存地址增大的方向。此外只能通过指针访问堆内存。

(3)shared library

  动态链接常用的库,如libc.so etc

(4)stack area

  栈,这部分内存由编译器来管理,主要来存储局部变量,处理函数调用,处理运算。栈的大小向下增长,内存地址减少的方向。

(5)kernel

  内核,操作系统的核心,就是它提供便利的接口,并且管理内存,进程等一系列资源。

  下面来看一下 JVM 内部是个什么样子,一图胜千言,如图所示:

图 2 JVM 内部结构

  JVM 加载字节码文件,然后一顿操作,就 load 到内存了,JVM内存整体上分为堆和非堆,下面是详细介绍:

(1) Heap

  堆,和C不同的是,Java不能直接操作它,而是由 JVM 自动管理。当我们使用关键字 new 一个对象实例,JVM就会为我们在堆上分配空间。基本所有的对象实例都在这里分配,那么问题就来了,怎么分配,怎么回收呢?为了方便管理堆内存,HotSpot VM 又把堆根据对象的不同生命周期分为年轻代(YoungGen)和老年代(OldGen)。YoungGen 又分为 Eden 区和两个大小一样的 Survivor(From,To),对象优先在 Eden 分配,当Eden无空闲空间,会进行一次 MinorGC,把 Eden 清空,把存活的对象从From复制到To。大部分对象生命周期都很短,时间长的根据它的年龄会提升到老年代,当老年代空间满了,会发生一次stop-the-world的FullGC,对整个堆进行回收。关于释放内存,JVM 会根据不同区域使用不同的回收策略,当然也可以手动指定垃圾收集器。

(2) JVM Stacks & PC Register

  栈,每个线程都唯一对应着一个栈和程序计数器,这里是线程的工作内存,这部分内存不由 JVM 管理,随线程生而生,死而死。程序计数器,主要作用就是存储指令地址,取指,解码和执行。栈的元素是栈帧,当调用方法时,创建一个栈帧并入栈,把当前对象的引用 this 存储在第 0 位置,然后依次存储方法的局部变量,返回地址以及动态链接运行时常量池。

(3) Metaspace

  元空间,JDK1.8移除了永久代,相关数据的移动情况是,将Class meta数据移动到Metaspace并存储在本地内存中,将Intern String 和类静态变量移动到了堆中,具体内容请查看 JEP 122: Remove the Permanent Generation

(4) Native Method Stacks

  JVM栈是为Java方法准备的,那么本地方法栈则是为虚拟机调用本地方法服务的。

2. JVM 内存管理

  JVM 通过Garbage Collection(以下简称GC)进行管理内存,GC 解决了大部分内存分配的问题。GC的主要职责有:

  • 分配内存
  • 确保引用对象保留在内存
  • 回收不可达引用对象的内存
2.1 内存分配

(1)Bump-the-pointer & TLABs

  对于一些垃圾回收器来说,如SerialGC、ParallelGC,大部分情况下都是有可用且比较大的连续内存。使用 bump-the-pointer 技术为对象分配空间,指针始终指向之前分配对象的末尾,当要分配新对象时,只需要检查该代剩余空间是否能够容纳次对象,如果能,就更新指针并初始化对象。bump有颠簸之意,而且每次分配对象的大小都不一样,指针增加忽快忽慢,不如译为颠簸指针。

  线性分配效率比较高,在多线程环境下,必须保证分配操作的安全性,一个简单的办法就是使用全局锁,但不可避免的会影响性能。HotSpot VM 采用一种 Thread-Local Allocation Buffers(TLABs)的技术来增加分配吞吐量,只有当线程需要一个新的 TLABs 时,才需要同步。此外TLABs在Eden分配。

(2)Free Memory List

  当使用 Concurrent Mark-Sweep (CMS) Collector 收集器时,它释放无用对象的空间后,并不压缩活的对象,也就是空闲内存是不连续的,就不能使用上述的简单指针的技术,就必须维护一个空间内存的列表,每次分配新对象时,搜索这个列表,由于内存不连续,对于大对象可能会找不到连续的内存来分配,所以 cms 所需的堆要更大一些。

(3)Object Header 对象头

  每个对象都有一个头信息,数组对象比普通对象多了一个长度如下:

图 3 对象头

2.2 如何判断对象是否可回收

  垃圾收集器如何判断对象能否被回收?常用的算法如下。

(1)引用计数法

  给对象添加一个引用计数器,每当有一个地方引用它时就加1,引用失效就减1,当计数器为0时,就说明这个对象为垃圾,可以被回收。引用计数法效率比较高,但是不能解决对象循环引用的问题,所以 JVM 没有使用这一方法。

(2)可达性分析

  JVM 维护着一个对象可达图,其形式如下:

图 4 可达性分析

  根节点,即GC Root对象,主要有以下几种:

  • JVM 栈引用的对象
  • 类静态属性和常量引用的对象
  • PC Register引用的对象
  • JNI引用的对象
2.3 回收算法和垃圾收集器
垃圾收集算法

(1)标记清除(Mark&Sweep)

  算法分为两个阶段:标记和清除阶段。首先标记所有可达的存活对象,然后清除不可达对象。清除后的内存是不连续的。

(2)标记压缩(Mark&Compact)

  与标记清除的区别是,标记完后,不直接清除,而是先把活的对象紧凑压缩到一块,然后在清除不可达对象。

(3)复制算法

  把内存分为相等的两部分,比如年轻代的Survivor区域的两个空间,每次分配时只使用其中的一块,当进行回收时,就将存活的对象复制到另一块,清空当前使用的。降低了内存的利用率。

(4)分代收集

  HotSpot就是这种算法,它不是新的算法,而是把堆分成不同区域,使用不同的回收方法。年轻代对象生命周期比较短,存活对象少,使用复制算法,老年代对象存活率比较高,使用标记清除或标记压缩算法。

垃圾收集器

(1)Serial

  串行垃圾收集器,是Client模式VM的默认收集器,stop-the-world,使用复制算法,而且只有一个GC线程。

(2)ParNew

  stop-the-world,使用复制算法,有多个 GC 线程。它可以和CMS收集器配合使用。

(3)Parallel Scavenge

  stop-the-world,使用复制算法,有多个 GC 线程。不能和CMS配合使用

(4)Serial Old

  老年代的串行收集器,使用标记压缩整理算法,也是 stop-the-world,并且只有一个GC线程。

(5)Parallel Old

  老年代并行垃圾收集器,多个 GC 线程,使用标记压缩整理算法。

(6)CMS & G1

  并发垃圾收集器,JVM 停顿时间比较短,相对来说程序响应快。

常用 GC 参数
Option Garbage Collector Selected Note
–XX:+UseParNewGC ParNew + Serial Old  
–XX:+UseSerialGC Serial + Serial Old Serial
–XX:+UseParallelGC Parallel Scavenge + Serial Old Parallel(Default)
–XX:+UseParallelOldGC Parallel Scavenge + Parallel Old Parallel compacte
–XX:+UseConcMarkSweepGC ParNew + CMS + Serial Old 如果使用CMS出现错误,则使用Serial Old
–XX:+PrintGC 输出 GC 基本信息  
–XX:+PrintGCDetails 输出 GC 详细信息 常用
–XX:+PrintGCTimeStamps 输出 GC 开始时间戳  

3. JVM 内存模型

TODO

JVM-内存管理的更多相关文章

  1. JVM内存管理------垃圾搜集器参数精解

    本文是GC相关的最后一篇,这次LZ只是罗列一下hotspot JVM中垃圾搜集器相关的重点参数,以及各个参数的解释.废话不多说,这就开始. 垃圾搜集器文章传送门 JVM内存管理------JAVA语言 ...

  2. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  3. JVM内存管理(二)

    JVM内存管理          JVM在执行java程序的过程中,会把内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖 ...

  4. JVM内存管理及垃圾回收

    一.JVM内存的构 Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法.总体分为下面几个部分: 程序计数器(Progra ...

  5. 现代JVM内存管理方法的发展历程,GC的实现及相关设计概述(转)

    JVM区域总体分两类,heap区和非heap区.heap区又分:Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分: ...

  6. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  7. java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

    概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...

  8. Java的内存 -JVM 内存管理

    一.综述 如果你学过C或者C++,那么你应该感受过它们对内存那种强大的掌控力.但是强大的能力往往需要更强大的控制力才能保证能力不被滥用,如果滥用C/C++的内存管理那么很容易出现指针满天飞的情况,不出 ...

  9. JVM内存管理及垃圾回收【转】

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  10. 你应该这样理解JVM内存管理

    在进行Java程序设计时,一般不涉及内存的分配和内存回收的相关代码,此处引用一句话: Java和C++之间存在一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外的人想进去,墙里面的人想出来 ,个人从这 ...

随机推荐

  1. 最详细的网站改版SEO优化指南:如何让排名不降反升

    我知道,网站改版很是让人头疼.首先,这个过程需要很长时间还有大量工作要做,并且通常结果不会如你的预期.其次,改版确实有破坏之前为 SEO 所做努力的风险. 但不要因为通常网站改版带来排名下降就认为这是 ...

  2. 【集合框架】JDK1.8源码分析之HashMap(一)

    一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也 ...

  3. 会务准备期间材料准备工作具体实施总结 ----(vim技巧应用, python信息提取与整合, microsoft word格式调整批量化)

    会务准备期间材料准备工作具体实施总结(vim, python, microsoft word) span.kw { color: #007020; font-weight: bold; } code ...

  4. 设置 LongListSelector 只有在项多的时候才分组

    Windows Phone 中的控件LongListSelector是一个很好的分组聚类控件,当列表中数据特别多的时候,LongListSelector就像字典中的目录,让我们很快定位到要找的数据. ...

  5. Python字典实现分析

    背景介绍 最近使用Python开发项目为主,当使用到字典时感觉非常方便实用.那么好奇心就驱使我要搞清楚字典是怎么实现的.为了真正的搞清楚字典的实现就不得不使用C语言来实现一遍,为此我查了一些资料现在总 ...

  6. IntelliJ IDEA 转移C盘.IntelliJIdea(索引目录)

    转移原因: C盘是机械硬盘,并且容量不多的情况下,建议转移. 转移步骤: 找到索引目录 win10系统下默认路径:C:\Users\asus\.IntelliJIdea2016.2 *复制或剪切到新的 ...

  7. Xamarin.Android之给我们的应用加点过渡效果

    零.前言 试想一下,我们的应用正在请求一些数据,假设网络不是很好,要花比较长的时间等待,这个时候界面什么反应也没有, 一动不动,用户可能就会认为应用挂掉了,这么久都没反应的,说不定下一分钟用户就把它卸 ...

  8. 适配器模式 - Adapter

    Adapter Pattern, 适用场景: 接口匹配兼容: 客户代码统一调用同一接口: 在.NET中,DataAdapter用作DataSet和数据源之间的适配器以保存和检索数据. 参考:

  9. struts2+spring的两种整合方式

    也许有些人会因为学习了struts1,会以为struts2.struts1与spring的整合也是一样的,其实这两者相差甚远.下面就来讲解一下struts2与spring的整合两种方案.(部分转载,里 ...

  10. 对,这是http处理层

    16年2月的一次代码重构,面对如此肮脏丑陋的代码我困在了座椅上整整一天的时间. 底层用java写的api接口,通过http去调用,在之上是用php写的业务逻辑层,重构的代码,正是php这一层. pub ...