文档章节

Java架构师第一步——JVM内存管理(读书笔记)

ND小龙
 ND小龙
发布于 2017/06/12 23:55
字数 2372
阅读 33
收藏 0

Java与C++之间有一堵由内存动态分配和垃圾收集技术所组成的“围城”,墙外面的人想 进去,墙里面的人却想出来。

对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力,又担负着每 一个对象生命开始到终结的维护责任。

对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操 作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,由虚拟机管理内存这 一切看起来都很美好。不过,也正是因为Java程序员把内存控制的权力交给了Java虚拟机, 一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误 将会成为一项异常艰难的工作。

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

对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。 Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就 是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java堆

Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”(Garbage Collected Heap)

从内存回收的角度来看,由于现在收集器基 本都采用分代收集算法,

所以Java堆中还可以细分为:新生代和老年代;

 

再细致一点的有 Eden空间、From Survivor空间、To Survivor空间等。从内存分配的角度来看,线程共享的 Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

 

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上 是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是 可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如 果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

 

其他内存:程序计数器、Java虚拟机栈、本地方法栈、方法区、运行时常量池、直接内存

 

对象创建

Java程序运行过程中无时无刻都有对象被创建,虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一 个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没 有,那必须先执行相应的类加载过程。

 

假设Java堆中内存是绝对规整的,所有用过的内 存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配 内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称 为“指针碰撞”(Bump the Pointer)。

 

如果Java堆中的内存并不是规整的,已使用的内存和空 闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记 录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。

 

Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达 路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

可通过Eclipse Memory Analyzer查看堆转储快照文件,

如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链。能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们的。比较准确地定位出泄露代码的位置。

 

虚拟机栈和本地方法栈溢出

实验结果表明:在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无 法分配的时候,虚拟机抛出的都是StackOverflowError异常。

 

方法区和运行时常量池溢出

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述 等。对于这些区域的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。

 

可达性分析算法

这个算法的基本思 路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所 走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连 (用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。对象虽然互相有关联,但是它们到GC Roots是不可达 的,所以它们将会被判定为是可回收的对象。

 

垃圾收集算法

标记-清除算法

复制算法

标记-整理算法

 

Serial收集器

Serial收集器是一个单线程的收集器,但它 的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作, 更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

 

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。

 

CMS收集器

整个过程分为4个步骤:

初始标记(CMS initial mark)

并发标记(CMS concurrent mark)

重新标记(CMS remark)

并发清除(CMS concurrent sweep)

 

G1收集器

G1把内存“化整为零”,

初始标记(Initial Marking)

并发标记(Concurrent Marking)

最终标记(Final Marking)

筛选回收(Live Data Counting and Evacuation)

 

启动附加参数

“-Dcom.sun.management.jmxremote” JMX管理功能

jstat:监视

jinfo:配置信息

jmap:Java内存映像工具

jhat(JVM Heap Analysis Tool):虚拟机堆转储快照分析工具

jstack:Java堆栈跟踪工具

MonitoringTest

 

从实践经验的角度出发,除了Java堆和永久代之外,我们注意到下面这些区域还会占用 较多的内存,这里所有的内存总和受到操作系统进程最大内存的限制。

Direct Memory:可通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出 OutOfMemoryError或者OutOfMemoryError:Direct buffer memory。

线程堆栈:可通过-Xss调整大小,内存不足时抛出StackOverflowError(纵向无法分配, 即无法分配新的栈帧)或者OutOfMemoryError:unable to create new native thread(横向无法 分配,即无法建立新的线程)。

Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB内 存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会抛出IOException:Too many open files异常。

 

不恰当数据结构导致内存占用过大

例如,有一个后台RPC服务器,使用64位虚拟机,内存配置为-Xms4g-Xmx8g-Xmn1g, 使用ParNew+CMS的收集器组合。平时对外服务的Minor GC时间约在30毫秒以内,完全可以 接受。但业务上需要每10分钟加载一个约80MB的数据文件到内存进行数据分析,这些数据 会在内存中形成超过100万个HashMap<Long,Long>Entry,在这段时间里面Minor GC就会造 成超过500毫秒的停顿,对于这个停顿时间就接受不了了。

如果不修改程序,仅从GC调优的角度去解决这个问题,可以考虑将Survivor空间去掉 (加入参数-XX:SurvivorRatio=65536、-XX:MaxTenuringThreshold=0或者-XX: +AlwaysTenure),让新生代中存活的对象在第一次Minor GC后立即进入老年代,等到Major GC的时候再清理它们。这种措施可以治标,但也有很大副作用,治本的方案需要修改程 序,因为这里的问题产生的根本原因是用HashMap<Long,Long>结构来存储数据文件空间效率太低。

实际耗费的内存为 (Long(24B)×2)+Entry(32B)+HashMap Ref(8B)=88B,空间效率为16B/88B=18%

 

 

 

本文转载自:《Java虚拟机(第二版)》

ND小龙
粉丝 12
博文 45
码字总数 2295
作品 0
福州
私信 提问
用思维导图来学java虚拟机,轻松易懂!

说明 本篇文章是对周志明的《深入理解Java虚拟机》的读书笔记,思维导图使用Mindjet MindManager。曾经看到过这样一句话: 关于教育,有一个经典的定义是:把在学校里学到的东西全部忘掉,最...

小刀爱编程
2018/10/12
169
0
读书笔记之《Java并发编程的艺术》-并发编程基础

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
4K
8
读书笔记之《Java并发编程的艺术》-并发编程容器和框架(重要)

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
718
1
Android--面试中遇到的问题总结(三)

《Android 开发工程师面试指南 LearningNotes 》,作者是陶程,由梁观全贡献部分。大家可以去知乎关注这两位用心的少年。这份指南包含了大部分Android开发的基础、进阶知识,不仅可以帮助准备...

sealin
2017/02/22
0
0
读书笔记之《Java并发编程的艺术》-线程池和Executor的子孙们

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
752
1

没有更多内容

加载失败,请刷新页面

加载更多

Kafka再平衡机制详解

所谓的再平衡,指的是在kafka consumer所订阅的topic发生变化时发生的一种分区重分配机制。一般有三种情况会触发再平衡: consumer group中的新增或删除某个consumer,导致其所消费的分区需要...

爱宝贝丶
30分钟前
23
0
element 验证 请输入大于0的整数

data() { var validatePass = (rule, value, callback) => { // if (value <= 0) { // callback(new Error('请输入大于0的整数')); // } else { // c......

沉迷代码我爱学习
41分钟前
4
0
报表工具花钱or开源?我对比了这6个工具

近一年都在处理报表问题,调研了不少报表工具,也开发了适合公司业务的报表应用。分享一些关于如何选择报表工具的个人观点,希望对你有参考作用。 对于大部分企业来说,能花时间和人力去开发...

帆软
42分钟前
3
0
自建redis笔记--Redis cluster搭建

Redis cluster搭建 2018年十月 Redis 发布了稳定版本的 5.0 版本,推出了各种新特性,其中一点是放弃 Ruby的集群方式,改为 使用 C语言编写的 redis-cli的方式,是集群的构建方式复杂度大大降...

北极之北
42分钟前
3
0
分享一个在caffe中实现的yolo层

这是别人实现的,是我移植到cc的cpu实现,可以实现caffe中使用yolo3,但是我感觉实际效果不如darknet 好点 template <typename Dtype>inline Dtype sigmoid(Dtype x){return 1. / (1. ...

开飞色
44分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部