JVM 是什么呢?说的直白点就是 Java 代码运行的地方,全称 Java Virtural Machine,Java 虚拟机。有的人就会奇怪了,为什么 Java 程序员需要了解这个东西?毕竟大多数情况下,“能跑”就行。

能跑真的行吗?你说在一个小公司里,“能跑”就行那是肯定的,业务必定是优先的。可是发展到规模大了之后,“能跑”好像就没那么简单了。规模大了,程序突然崩溃了,却不知道什么情况,这可以叫“能跑”吗?好像不是这样。所以成熟一些公司招程序员,肯定是要懂 JVM 的,不然代码写出了问题,JVM 崩了,都不知道是为什么——打个比方,系统抛出了一个 OOM 了,原因为 null(还真不是 heap),某程序员却不知道为什么 OOM 了,这显然不合适。或许某程序员觉得,这个东西交给运维、或者处在牛 A 和牛 B 之间的人。如果这么想的话,你的解决问题的能力永远无法提升。

那为什么 Java 有虚拟机,C++ 没有呢?因为 C++ 自己管理指针,而 Java 不需要你管理指针—— JVM 帮你管理了。不需要管理指针可以让你的代码更加面向对象(面向对象这几个字,天天被人吹,估计也没几个人懂,有的人竟然吹面向对象而不知道设计模式),让你专注于业务代码。这一切的基础,是你要知道指针是什么。

当然知道指针是什么也是一件复杂的事情。从 C++ 来看,指针可能是 *、& 相关运算符,可是初学的人往往搞不懂设计这些运算符的原因。所以计算机有一门课程叫做汇编,汇编会告诉你这些运算符是怎么来的——和内存地址有关。这就是计算机专业和非计算机专业的不同——计算机专业知道代码时怎么跑的,而非计算机专业不懂得这些,然而这些会限制一个人能不能越走越远。比方说我看《深入理解 Java 虚拟机》难度并不是很高,可能对于不了解指针的人来说,不了解内存的人来说,这本书写的让人感觉云里雾里。

那管理指针有什么用呢?记住了指针,就能记住这块区域存的是什么。执行一段 A 代码,存了一些数据;执行一段 B 代码,存了一些数据;A 代码执行完了,存的那些数据也没有意义了——就是垃圾。垃圾是要回收的,在有限的内存里,垃圾会一直积累,直到占满所有的内存。JVM 帮你做了这些。

那 JVM 是怎么保存这些数据的呢?如果让各个人做可能有不同的方法,现在假设我们有十块内存空间。每一个对象可能占用一块或者多块空间。假设,对象 A 保存第一块,对象 B 保存第二块、第三块。这个时候,程序员小 A 觉得累了,明天再来看这篇文章(看完这段话假设已经过了一天)。第二天,程序员小 A 发现自己记不得那块保存了什么对象、哪个对象占了那几块。这个时候需要一个数据结构去保存对象的位置——也就是对象开始的指针。小 B (就你事情多)这之后问了,我再对象 A 所占块之后空一个区域,表示这个对象结束了,不行吗?那我就想问一句,空对象小 B 你怎么表示呢?小 B 说,空对象用特殊标记啊!那这个特殊标记又怎么特殊标记呢,etc......似乎用空区域表示对象结束不是一个好办法。

所以现在有两个概念——一个是用来存储对象的,一个是用来存储引用的。这两个不同性质的概念应该存在不同的区域。在真实的 Java 虚拟机里,也是这么分的,简单的来说,引用存在 VM Stack 里,实例对象存在 Heap 里。当然,还有一些区域存着别的东西,Native Method Stack(NMS)、Programme Counter Register(PCR)、Method Area(MA)等。你的程序不可能只保存数据,类、方法信息需要保存在 MA 中;你程序执行到哪里了,保存在 PCR 中;Java 有一些代码需要用 C++ 实现,C++ 对象所需要的空间,保存在 NMS 中。

话说回来,我们保存了这些对象,现在需要回收垃圾了。那就涉及到一些问题:

  • 回收什么?——垃圾
  • 怎么找到所有的垃圾?
  • 怎么回收?

怎么找到所有的垃圾?拿得从,程序是怎么跑起来的来看。Java 程序是多线程的,只要跑起来肯定有线程,这些线程的信息被保存到 VM Stack 中。所以什么对象是活跃的?在 VM Stack 中对象肯定是活跃的,这些肯定不是垃圾。还有一些被 static 引用的也不是垃圾,因为很可能下次就会被其他人引用。

听起来似乎简单,实际上并没有那么简单—— VM Stack 里保存了活跃对象 A,A 保存了 B、D,B 保存了 C 等等。这样的结构是树。如何遍历一颗树找到所有的结点那是就是树相关的算法——深度优先搜索或者广度优先搜索了,涉及到数据结构和算法的知识,这里就不多说了。这在 Java 虚拟机中称之为可达性算法。

相对于可达性算法,还有一种算法叫引用计数法。我们维护一个引用列表,A 引用了 B,A 引用次数 + 1;如果没人引用,那肯定是垃圾。似乎这也算起来更简单些,但是小 C 发现了问题:如果有人引用,那它就不是垃圾了吗?其实,存在 A 引用 B, B 引用 A,但是所有运行着的线程都不需要这两个对象,所以这两个也是垃圾。所以引用计数法不是特别靠谱。

说一件事情可能让各位很惊悚,Python 在用引用计数法。引用计数法不会让虚拟机“停下”,可以一边执行一边计算垃圾;而其他的算法,例如标记—清除法,需要实时计算引用,会暂停所有线程去分析。Java 有个著名的问题——Stop The World,STW。在 GC 的时候,服务是不可用的——这对于普通业务来说,还好;对于游戏来说,这是不可接受的,通常这个时间至少达到 100 ms。打个农药突然给你增加延迟 100 ms,你能接受吗?所以,永远没有银弹。

接下来,是怎么回收。想一想现实中垃圾是怎么清楚的,对,现实中。地上有一堆垃圾,丢掉就行了呗。好的,最原始的垃圾清楚方式就是标记—清除。我知道这个地方没用,我就把引用扔掉。这有个问题,比如你有十片地方,空着的地方是 0,没空的地方是 1。原来的分布式是 1111111111,垃圾清楚之后,现在的地方分布式 1010101010。好了,现在来了个大家伙,要占用两个位置,发现没地方放了。果真是没地方放吗?这就和收拾家里一样,挪挪总是有位置摆东西的,我收拾成 1111100000 不就好了,这样我就有两个位置了。这就是标记—清除的缺点——会产生内存碎片。小 D 这时候会说,那我不清除了,我直接把有用的复制到前面来不就好了吗?是啊,这就是标记—整理算法。

然而,90% 的对象都是朝生夕死的,就跟家里的东西一样,大部分都是用完就扔的。这个时候就有人提出了复制算法。具体算法不想多说了,简要来说就是分成两个区域,一个区域保存存活的对象 + 后来产生的对象;另一个区域空下,为下次 GC 做准备,这个区域通常很小,为 10%,正好是存活对象的比例。这样的算法实现起来简单,只要复制到空闲区域,不用担心区域内是不是已经内容、如何移动内容(可能还需要多次移动),而标记—整理算法显然要考虑这些。

JVM 还做了更多的优化,根据对象不同的属性,分成老年代、年轻代,当然算法还不一样。此外还进化出 CMS、G1 算法,这里一句两句讲不清楚,这里就不多提了,为了吞吐量而优化。但一切的一切,有一条重要的原则是 JVM 开发者没有遗忘的——根据实际情况出发。为什么会产生分代?实际情况就是很多对象朝生夕死。

这篇漫谈主要说的是 JVM 内存相关的一些东西,想到什么就写了什么,主要讲了自己的一些感受,具体、准确的分析还请看书、看资料。

漫谈 JVM —— 内存的更多相关文章

  1. 漫谈 JVM —— 内存模型、线程、锁

    Java 内存模型(JMM),实际上的目的就是为了统一内存管理.这让我想到了,作为一个程序员总是想着有银弹,有一个代码能万能的在所有场景上.经过多次尝试我发现这是不可能的:需求在变,技术在更新,没有什 ...

  2. jvm内存结构及对象漫谈(较全)

    最近想整理一下GC相关的知识和经验,在整理之前先整理一下jvm的内存结构,后续会持续更新. jvm内存结构重要由两部分组成:线程共享区域与线程私有区域,如下图所示: 其中方法区和堆为线程共享区域,栈与 ...

  3. (转)漫谈JVM

    漫谈JVM 原文:https://liuzhengyang.github.io/2016/10/05/gossip-jvm/ 背景介绍 JVM已经是Java开发的必备技能了,JVM相当于Java的操作 ...

  4. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  5. jvm系列(二):JVM内存结构

    JVM内存结构 所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能 ...

  6. jvm内存溢出分析

    概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...

  7. jvm内存区域

    概述 jvm内存分为几个区域: 程序计数器 虚拟机栈 本地方法栈 堆 方法区 运行时常量池 直接内存 这些内存区域是在java进程中细分的,为java程序提供服务 不同的区域存储的内容不一样,生命周期 ...

  8. JVM内存管理&GC

    一.JVM内存划分 |--------------------|-------------PC寄存器-------| |----方法区 ---------|--------------java 虚拟机 ...

  9. 漫谈JVM

    背景介绍 JVM已经是Java开发的必备技能了,JVM相当于Java的操作系统. JVM,java virtual machine, 即Java虚拟机,是运行java class文件的程序. Java ...

随机推荐

  1. vs 外部依赖项、附加依赖项以及如何添加依赖项目

    我们在 VS 中创建 Win32 控制台应用程序,vs 会为解决方案创建默认地创建 4 个 filters(资源管理器中没有对应的目录和文件夹): 头文件:一般为 .h 文件 外部依赖项 源文件:一般 ...

  2. <Linux> xm 命令

    xm console     <域ID>         ctrl+ ] 退出虚拟机到宿主 xm reboot      <域ID> xm pause       <域I ...

  3. Redis Service

    https://raw.githubusercontent.com/MSOpenTech/redis/3.0/Windows%20Service%20Documentation.md

  4. 在TMemo上画一条线(超级简单,举一反三)

    var C:TControlCanvas; begin C := TControlCanvas.Create; C.Pen.Color := clRed; C.Pen.Width := ; C.Con ...

  5. SpringMVC大坑一枚:ContentNegotiatingViewResolver可能不利于SEO

    广大站长都有关注自己网站被搜索引擎收录的习惯,最近用百度.360等搜索引擎,查看了自己网站的一些情况,使用命令"site:fansunion.cn". 我发现了一些异常信息,不止一 ...

  6. 【codeforces 602D】Lipshitz Sequence

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  7. Qt for WebAssembly

    To complete the list of Qt framework items, let’s talk a bit about our ongoing research of Qt for We ...

  8. js进阶 9-8 html标签如何实现禁止复制和粘贴

    js进阶 9-8  html标签如何实现禁止复制和粘贴 一.总结 一句话总结: 1.在oncopy方法中return false即可阻止在控件中复制内容 2.在onpaste方法中return fal ...

  9. gen_server的enter_loop分析

    http://my.oschina.net/astute/blog/119250?p=1 在看ranch user guide的过程中,发现实现protocol handler需要使用特殊的gen_s ...

  10. [GeekBand ] 利用 pass by reference -to -const 编写高效规范的 c++代码

    本文参考资料 :  GeekBand 侯捷老师,学习笔记 Effective C ++ 侯捷译 条款20 开发环境采用:VS2013版本 首先:分析值传递的缺点 (一) class Person{ p ...