漫谈 JVM —— 内存
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 —— 内存的更多相关文章
- 漫谈 JVM —— 内存模型、线程、锁
Java 内存模型(JMM),实际上的目的就是为了统一内存管理.这让我想到了,作为一个程序员总是想着有银弹,有一个代码能万能的在所有场景上.经过多次尝试我发现这是不可能的:需求在变,技术在更新,没有什 ...
- jvm内存结构及对象漫谈(较全)
最近想整理一下GC相关的知识和经验,在整理之前先整理一下jvm的内存结构,后续会持续更新. jvm内存结构重要由两部分组成:线程共享区域与线程私有区域,如下图所示: 其中方法区和堆为线程共享区域,栈与 ...
- (转)漫谈JVM
漫谈JVM 原文:https://liuzhengyang.github.io/2016/10/05/gossip-jvm/ 背景介绍 JVM已经是Java开发的必备技能了,JVM相当于Java的操作 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- jvm系列(二):JVM内存结构
JVM内存结构 所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能 ...
- jvm内存溢出分析
概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...
- jvm内存区域
概述 jvm内存分为几个区域: 程序计数器 虚拟机栈 本地方法栈 堆 方法区 运行时常量池 直接内存 这些内存区域是在java进程中细分的,为java程序提供服务 不同的区域存储的内容不一样,生命周期 ...
- JVM内存管理&GC
一.JVM内存划分 |--------------------|-------------PC寄存器-------| |----方法区 ---------|--------------java 虚拟机 ...
- 漫谈JVM
背景介绍 JVM已经是Java开发的必备技能了,JVM相当于Java的操作系统. JVM,java virtual machine, 即Java虚拟机,是运行java class文件的程序. Java ...
随机推荐
- vs 外部依赖项、附加依赖项以及如何添加依赖项目
我们在 VS 中创建 Win32 控制台应用程序,vs 会为解决方案创建默认地创建 4 个 filters(资源管理器中没有对应的目录和文件夹): 头文件:一般为 .h 文件 外部依赖项 源文件:一般 ...
- <Linux> xm 命令
xm console <域ID> ctrl+ ] 退出虚拟机到宿主 xm reboot <域ID> xm pause <域I ...
- Redis Service
https://raw.githubusercontent.com/MSOpenTech/redis/3.0/Windows%20Service%20Documentation.md
- 在TMemo上画一条线(超级简单,举一反三)
var C:TControlCanvas; begin C := TControlCanvas.Create; C.Pen.Color := clRed; C.Pen.Width := ; C.Con ...
- SpringMVC大坑一枚:ContentNegotiatingViewResolver可能不利于SEO
广大站长都有关注自己网站被搜索引擎收录的习惯,最近用百度.360等搜索引擎,查看了自己网站的一些情况,使用命令"site:fansunion.cn". 我发现了一些异常信息,不止一 ...
- 【codeforces 602D】Lipshitz Sequence
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...
- Qt for WebAssembly
To complete the list of Qt framework items, let’s talk a bit about our ongoing research of Qt for We ...
- js进阶 9-8 html标签如何实现禁止复制和粘贴
js进阶 9-8 html标签如何实现禁止复制和粘贴 一.总结 一句话总结: 1.在oncopy方法中return false即可阻止在控件中复制内容 2.在onpaste方法中return fal ...
- gen_server的enter_loop分析
http://my.oschina.net/astute/blog/119250?p=1 在看ranch user guide的过程中,发现实现protocol handler需要使用特殊的gen_s ...
- [GeekBand ] 利用 pass by reference -to -const 编写高效规范的 c++代码
本文参考资料 : GeekBand 侯捷老师,学习笔记 Effective C ++ 侯捷译 条款20 开发环境采用:VS2013版本 首先:分析值传递的缺点 (一) class Person{ p ...