文档章节

谈谈Java内存管理

Galy_绿
 Galy_绿
发布于 2016/07/11 12:00
字数 4284
阅读 26
收藏 2
点赞 0
评论 0

对于一个Java程序员来说,大多数情况下的确是无需对内存的分配、释放做太多考虑,对Jvm也无需有多么深的理解的。

 

但是在写程序的过程中却也往往因为这样而造成了一些不容易察觉到的内存问题,并且在内存问题出现的时候,也不能很快的定位并解决。

 

因此,了解并掌握Java的内存管理是一个合格的Java程序员必需的技能,也只有这样才能写出更好的程序,更好地优化程序的性能。

 

一、背景知识


 

根据网络可以找到的资料以及笔者能够打听到的消息,目前国内外著名的几个大型互联网公司的语言选型概括如下:

 

  • Google: C/C++ Go Python Java JavaScript,不得不提的是Google贡献给java社区的guava包质量非常高,非常值得学习和使用。

  • Youtube、豆瓣: Python

  • Fackbook、Yahoo、Flickr、新浪:php(优化过的php vm)

  • 网易、阿里、搜狐: Java、PHP、Node.js

  • Twitter: Ruby->Java,之所以如此就在于与Jvm相比,Ruby的runtime是非常慢的。并且Ruby的应用比起Java还是比较小众的。不过最近twitter有往scala上迁移的趋势。

 

可见,虽然最近这些年很多言论都号称java已死或者不久即死,但是Java的语言应用占有率一直居高不下。

 

与高性能的C/C++相比,Java具有gc机制,并且没有那让人望而生畏的指针,上手门槛相对较低;而与上手成本更低的PHP、Ruby等脚本语言来说,又比这些脚本语言有性能上的优势(这里暂时忽略FB自己开发的HHVM)。

 

对于Java来说,最终是要依靠字节码运行在jvm上的。目前,常见的jvm有以下几种:

 

  • Sun HotSpot

  • BEA Jrockit

  • IBM J9

  • Dalvik(Android)

 

其中以HotSpot应用最广泛。目前sun jdk的最新版本已经到了8,但鉴于新版的jdk使用并未普及,因此本文仅仅针对HotSpot虚拟机的jdk6来讲。

 

二、Jvm虚拟机内存简介


 

>>2.1 Java运行时内存区

 

Java的运行时内存组成如下图所示:

 

 

其中,对于这各个部分有一些是线程私有的,其他则是线程共享的。

 

线程私有的如下:

 

①程序计数器

当前线程所执行的字节码的行号指示器

 

②Java虚拟机栈

Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。

 

  • 每个线程都有自己独立的栈空间

  • 线程栈只存基本类型和对象地址

  • 方法中局部变量在线程空间中

 

③本地方法栈

Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。

 

线程共享的如下:

 

①Java堆

存放对象实例,几乎所有的对象实例以及其属性都在这里分配内存。

 

②方法区

存储已经被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。

 

③运行时常量池

方法区的一部分。用于存放编译期生成的各种字面量和符号引用。

 

④直接内存

NIO、Native函数直接分配的堆外内存。DirectBuffer引用也会使用此部分内存。

 

>>2.2 对象访问

 

Java是面向对象的一种编程语言,那么如何通过引用来访问对象呢?一般有两种方式:

 

①通过句柄访问

 

 

①直接指针

 

 

此种方式也是HotSpot虚拟机采用的方式。

 

>>2.3 内存溢出

 

在JVM申请内存的过程中,会遇到无法申请到足够内存,从而导致内存溢出的情况。一般有以下几种情况:

 

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

 

  • StackOverflowError: 线程请求的栈深度大于虚拟机所允许的最大深度(循环递归)

  • OutOfMemoryError: 虚拟机在扩展栈是无法申请到足够的内存空间,一般可以通过不停地创建线程引起此种情况

 

②Java堆溢出: 当创建大量对象并且对象生命周期都很长的情况下,会引发OutOfMemoryError

 

③运行时常量区溢出:OutOfMemoryError:PermGen space,这里一个典型的例子就是String的intern方法,当大量字符串使用intern时,会触发此内存溢出

 

④方法区溢出:方法区存放Class等元数据信息,如果产生大量的类(使用cglib),那么就会引发此内存溢出,OutOfMemoryError:PermGen space,在使用Hibernate等框架时会容易引起此种情况。

 

三、垃圾收集


 

>>3.1 理论基础

 

在通常情况下,我们掌握java的内存管理就是为了应对网站/服务访问慢,慢的原因一般有以下几点:

 

  • 内存:垃圾收集占用cpu;放入了太多数据,造成内存泄露(java也是有这种问题的^_^)

  • 线程死锁

  • I/O速度太慢

  • 依赖的其他服务响应太慢

  • 复杂的业务逻辑或者算法造成响应的缓慢

 

其中,垃圾收集对性能的影响一般有以下几个:

 

  • 内存泄露

  • 程序暂停

  • 程序吞吐量显著下降

  • 响应时间变慢

 

先来看垃圾收集的一些基本概念

 

  • Concurrent Collector:收集的同时可运行其他的工作进程

  • Parallel Collector: 使用多CPU进行垃圾收集

  • Stop-the-word(STW):收集时必须暂停其他所有的工作进程

  • Sticky-reference-count:对于使用“引用计数”(reference count)算法的GC,如果对象的计数器溢出,则起不到标记某个对象是垃圾的作用了,这种错误称为sticky-reference-count problem,通常可以增加计数器的bit数来减少出现这个问题的几率,但是那样会占用更多空间。一般如果GC算法能迅速清理完对象,也不容易出现这个问题。

  • Mutator:mutate的中文是变异,在GC中即是指一种JVM程序,专门更新对象的状态的,也就是让对象“变异”成为另一种类型,比如变为垃圾。

  • On-the-fly:用来描述某个GC的类型:on-the-fly reference count garbage collector。此GC不用标记而是通过引用计数来识别垃圾。

  • Generational gc:这是一种相对于传统的“标记-清理”技术来说,比较先进的gc,特点是把对象分成不同的generation,即分成几代人,有年轻的,有年老的。这类gc主要是利用计算机程序的一个特点,即“越年轻的对象越容易死亡”,也就是存活的越久的对象越有机会存活下去(姜是老的辣)。

 

牵扯到垃圾收集,还需要搞清楚吞吐量与响应时间的含义

 

  • 吞吐量是对单位时间内完成的工作量的量度。如:每分钟的 Web 服务器请求数量

  • 响应时间是提交请求和返回该请求的响应之间使用的时间。如:访问Web页面花费的时间

 

吞吐量与访问时间的关系很复杂,有时可能以响应时间为代价而得到较高的吞吐量,而有时候又要以吞吐量为代价得到较好的响应时间。而在其他情况下,一个单独的更改可能对两者都有提高。

 

通常,平均响应时间越短,系统吞吐量越大;平均响应时间越长,系统吞吐量越小; 但是,系统吞吐量越大, 未必平均响应时间越短;因为在某些情况(例如,不增加任何硬件配置)吞吐量的增大,有时会把平均响应时间作为牺牲,来换取一段时间处理更多的请求。

 

针对于Java的垃圾回收来说,不同的垃圾回收器会不同程度地影响这两个指标。例如:并行的垃圾收集器,其保证的是吞吐量,会在一定程度上牺牲响应时间。而并发的收集器,则主要保证的是请求的响应时间。

 

对于GC(垃圾回收)的流程的基本描述如下:

 

  • 找出堆中活着的对象

  • 释放死对象占用的资源

  • 定期调整活对象的位置

 

GC算法一般有以下几种:

 

  • Mark-Sweep 标记-清除

  • Mark-Sweep-Compact 标记-整理

  • Copying Collector 复制算法

  • Mark-标记

  • 从”GC roots”开始扫描(这里的roots包括线程栈、静态常量等),给能够沿着roots到达的对象标记为”live”,最终所有能够到达的对象都被标记为”live”,而无法到达的对象则为”dead”。效率和存活对象的数量是线性相关的。

  • Sweep-清除

  • 扫描堆,定位到所有”dead”对象,并清理掉。效率和堆的大小是线性相关的。

  • Compact-压缩

  • 对于对象的清除,会产生一些内存碎片,这时候就需要对这些内存进行压缩、整理。包括:relocate(将存货的对象移动到一起,从而释放出连续的可用内存)、remap(收集所有的对象引用指向新的对象地址)。效率和存活对象的数量是线性相关的。

  • Copy-复制

  • 将内存分为”from”和”to”两个区域,垃圾回收时,将from区域的存活对象整体复制到to区域中。效率和存活对象的数量是线性相关的。

 

其中,Copy对比Mark-sweep

 

  • 内存消耗:copy需要两倍的最大live set内存;mark-sweep则只需要一倍。

  • 效率上:copy与live set成线性相关,效率高;mark-sweep则与堆大小线性相关,效率较低。

 

分代收集是目前比较先进的垃圾回收方案

 

对于分代收集,有以下几个相关理论

 

  • 分代假设:大部分对象的寿命很短,“朝生夕死”,重点放在对年青代对象的收集,而且年青代通常只占整个空间的一小部分。

  • 把年青代里活的很长的对象移动到老年代。

  • 只有当老年代满了才去收集。

  • 收集效率明显比不分代高。

 

HotSpot虚拟机的分代收集,分为一个Eden区、两个Survivor去以及Old Generation/Tenured区,其中Eden以及Survivor共同组成New Generatiton/Young space。

 

 

  • Eden区是分配对象的区域。

  • Survivor是minor/younger gc后存储存活对象的区域。

  • Tenured区域存储长时间存活的对象。

 

分代收集中典型的垃圾收集算法组合描述如下:

 

  • 年青代通常使用Copy算法收集,会stop the world

  • 老年代收集一般采用Mark-sweep-compact, 有可能会stop the world,也可以是concurrent或者部分concurrent。

 

>>3.2 HotSpot垃圾收集器

 

上图即为HotSpot虚拟机的垃圾收集器组成。

 

Serial收集器:

 

  • -XX:+UserSerialGC参数打开此收集器

  • Client模式下新生代默认的收集器。

  • 较长的stop the world时间

  • 简单而高效

 

此收集器的一个工作流程如下如所示:

 

收集前:

 

收集后:

 

ParNew收集器:

 

  • -XX:+UserParNewGC

  • +UseConcuMarkSweepGC时默认开启

  • Serial收集器的多线程版本

  • 默认线程数与CPU数目相同

  • -XX:ParrallelGCThreads指定线程数目

 

对比Serial收集器如下图所示:

 

Parallel Scavenge收集器:

 

  • 新生代并行收集器

  • 采用Copy算法

  • 主要关注的是达到可控制的吞吐量,“吞吐量优先”

  • -XX:MaxGCPauseMillis -XX:GCTimeRAtion两个参数精确控制吞吐量

  • -XX:UseAdaptiveSizePolicy GC自适应调节策略

  • Server模式的默认新生代收集器

 

Serial Old收集器:

 

  • Serial的老年代版本

  • Client模式的默认老年代收集器

  • CMS收集器的后备预案,Concurrent Mode Failure时使用

  • -XX:+UseSerialGC开启此收集器

 

Parallel Old收集器:

 

  • -XX:+UseParallelGC -XX:+UseParallelOldGC启用此收集器

  • Server模式的默认老年代收集器

  • Parallel Scavenge的老年代版本,使用多线程和”mark-sweep”算法

  • 关注点在吞吐量以及CPU资源敏感的场合使用

  • 一般使用Parallel Scavenge + Parallel Old可以达到最大吞吐量保证

 

CMS收集器:

 

并发低停顿收集器

 

  • -XX:UseConcMarkSweepGC 开启CMS收集器,(默认使用ParNew作为年轻代收集器,SerialOld作为收集失败的垃圾收集器)

  • 以获取最短回收停顿时间为目标的收集器,重视响应速度,希望系统停顿时间最短,会和互联网应用。

 

四个步骤:

 

  • 初始标记 Stop the world: 只标记GC roots能直接关联到的对象,速度很快。

  • 并发标记:进行GC roots tracing,与用户线程并发进行

  • 重新标记 Stop the world:修正并发标记期间因程序继续运行导致变动的标记记录

  • 并发清除

 

对比serial old收集器如下图所示:

 

CMS有以下的缺点:

 

  • CMS是唯一不进行compact的垃圾收集器,当cms释放了垃圾对象占用的内存后,它不会把活动对象移动到老年代的一端

  • 对CPU资源非常敏感。不会导致线程停顿,但会导致程序变慢,总吞吐量降低。CPU核越多越不明显

  • 无法处理浮动垃圾。可能出现“concurrent Mode Failure”失败, 导致另一次full GC ,可以通过调整-XX:CMSInitiatingOccupancyFraction来控制内存占用达到多少时触发gc

  • 大量空间碎片。这个可以通过设置-XX:UseCMSCompacAtFullCollection(是否在full gc时开启compact)以及-XX:CMSFullGCsBeforeCompaction(在进行compact前full gc的次数)

 

G1收集器:

 

G1算法在Java6中还是试验性质的,在Java7中正式引入,但还未被广泛运用到生产环境中。它的特点如下:

 

使用标记-清理算法

 

  • 不会产生碎片

  • 可预测的停顿时间

  • 化整为零:将整个Java堆划分为多个大小相等的独立区域

  • -XX:+UseG1GC可以打开此垃圾回收器

  • -XX:MaxGCPauseMillis=200可以设置最大GC停顿时间,当然JVM并不保证一定能够达到,只是尽力。

 

 

>>3.3 调优经验

 

  • 需要打开gc日志并读懂gc日志:-XX:PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps

  • 垃圾回收的最佳状态是只有young gc,也就是避免生命周期很长的对象的存在。

  • 从young gc开始,尽量给年青代大点的内存,避免full gc

  • 注意Survivor大小

  • 注意内存墙:4G~5G

 

GC日志简介

 

 

  • 第一个箭头:35592K->1814K(36288K),箭头指向的是新生段的内存占用情况; - 第二个箭头:38508K->7792K(520256K),箭头指向的是回收后的内存占用情况。

  • 垃圾收集停顿时间:0.0336

 

老年代使用建议

 

①Parallel GC(-XX:+UseParallel[Old]GC)

 

  • Parallel GC的minor GC时间是最快的, CMS的young gc要比parallel慢, 因为内存碎片

  • 可以保证最大的吞吐量

 

②确实有必要才改成CMS或G1(for old gen collections)

 

开发建议

 

①小对象allocate的代价很小,通常10个CPU指令;收集掉新对象也非常廉价;不用担心活的很短的小对象

 

②大对象分配的代价以及初始化的代价很大;不同大小的大对象可能导致java堆碎片,尤其是CMS, ParallelGC 或 G1还好;尽量避免分配大对象

 

③避免改变数据结构大小,如避免改变数组或array backed collections / containers的大小;对象构建(初始化)时最好显式批量定数组大小;改变大小导致不必要的对象分配,可能导致java堆碎片

 

④对象池可能潜在的问题

  • 增加了活对象的数量,可能增加GC时间

  • 访问(多线程)对象池需要锁,可能带来可扩展性的问题

  • 小心过于频繁的对象池访问

 

四/Java7、8带来的一些变化


 

  • Java7带来的内存方面的一个很大的改变就是String常量池从Perm区移动到了Heap中。调用String的intern方法时,如果存在堆中的对象,则会直接保存对象的引用,而不会重新创建对象。

  • Java7正式引入G1垃圾收集器用于替换CMS。

  • Java8中,取消掉了方法区(永久代),使用“元空间”替代,元空间只与系统内存相关。

  • Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。可以使用-XX:+UseStringDeduplication这个JVM参数来试一下这个特性。

本文转载自:http://www.rowkey.me/blog/2016/05/07/javamm/

共有 人打赏支持
Galy_绿
粉丝 12
博文 133
码字总数 14908
作品 0
海淀
使用开源KgCMS新闻系统建站时的JAVA云主机选型

KgCMS是国内一款JAVA新闻管理系统,相对于很多JAVA类站群型新闻系统,kgCMS是一个单站型系统,因而使用起来比较简单,现在谈谈KgCMS做企业网站时,如何选取IDC主机。 国内JAVA类主机空间已相...

聚龙软件
2014/04/07
0
0
关于Java数据类型与底层内存运行机制

在这里谈谈Java在编译时,变量与常量在内存的机制。 总所周知,Java数据类型主要分为3种:基本数据类型、布尔类型、引用类型。 下面先看看这个语句: 1.String str=new String("Hello"); 2....

loki_lan
2013/12/27
0
0
《成神之路-基础篇》JVM——垃圾回收(已完结)

Java内存模型,Java内存管理,Java堆和栈,垃圾回收 本文是[《成神之路系列文章》][1]的第一篇,主要是关于JVM的一些介绍。 持续更新中 Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收 ...

05/05
0
0
JVM性能优化, Part 1 ―― JVM简介

众所周知,Java应用程序是运行在JVM上的,但是你对JVM有所了解么?作为这个系列文章的第一篇,本文将对经典Java虚拟机的运行机制做简单介绍,内容包括“一次编写,到处运行”的利弊、垃圾回收...

梁杰_Jack
2014/10/30
0
0
out就可以理解为System类的静态成员变量引用了PrintStream类的对象

面试题是实验室师兄面试提供的。两道题 题1:Java中System.out.print()如何理解? 查阅API文档,我们可以知道:System是Java中的一个类,而out是System类的成员 。 out属于java.io.PrintStre...

变小火
06/29
0
0
java.lang.management

java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。它同时允许从本地和远程对正在运行的 Java 虚拟机进行监视和管理。 主要接口: Clas...

snail-
2015/08/17
0
0
JVM内存结构 VS Java内存模型 VS Java对象模型

Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模型和...

Java架构
07/11
0
0
《成神之路-基础篇》JVM——JVM内存结构(已完结)

Java内存模型,Java内存管理,Java堆和栈,垃圾回收 本文是《成神之路系列文章》的第一篇,主要是关于JVM的一些介绍。 持续更新中 参考文章: Java虚拟机的内存组成以及堆内存介绍 Java堆和栈...

05/05
0
0
JVM系列一:JVM内存组成及分配

java内存组成介绍:堆(Heap)和非堆(Non-heap)内存 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”...

Yohance
2014/04/22
0
0
My java——JVM(内存域)三

续 My java——JVM(内存)二 写了一点JVM内存的一些操作的方法,和引出内存的分类。 是呀,java内存是我们在java编程中很少考虑到的,也没用真正的管理过。也许都知道JVM有自己的垃圾回收机...

tngou
2013/03/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

java 重写排序规则,用于代码层级排序

1.dataList 是个List<Map<String,Object>> 类型的数据,所以比较的时候是冲map中获取数据,并且数据不能为空。 2.dataList 类型是由自己定义的,new Comparator<Map<String,Object>> 也是对应......

轻量级赤影
5分钟前
0
0
分布式大型互联网企业架构!

摘要: 开发工具 1.Eclipse IDE:采用Maven项目管理,模块化。 2.代码生成:通过界面方式简单配置,自动生成相应代码,目前包括三种生成方式(增删改查):单表、一对多、树结构。生成后的代码...

明理萝
5分钟前
0
1
对MFC程序的一点逆向分析:定位按钮响应函数的办法

因为消息响应函数保存在AFX_MSGMAP_ENTRY数组中, 观察nMessage、nCode、nID、pfn利用IDA在rdata段中搜索即可, 在IDA中找到代码段基址0x401000,函数地址0x403140, 在WinDbg中运行!addre...

oready
5分钟前
0
0
阻抗匹配与史密斯(Smith)圆图基本原理

参考:http://bbs.eeworld.com.cn/thread-650695-1-1.html

whoisliang
11分钟前
0
0
maven配置文件分离

一、 简介 遇到很多次别人处理的项目,测试环境,本地开发和线上环境的配置不一样,每一次部署都要重新修改配置文件,提交审核代码,才能打包,非常不方便。 其实相信很多人都知道可以使用m...

trayvon
11分钟前
0
0
MacOS和Linux内核的区别

导读 有些人可能认为MacOS和Linux内核有相似之处,因为它们可以处理类似的命令和类似的软件。甚至有人认为苹果的MacOS是基于linux的。事实上,这两个内核的历史和特性是非常不同的。今天,我...

问题终结者
27分钟前
1
0
SpringBoot | 第八章:统一异常、数据校验处理

前言 在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响...

oKong
35分钟前
2
0
mysql高级

一、存储引擎 InnoDB MyISAM 比较 二、数据类型 整型 浮点数 字符串 时间和日期 三、索引 索引分类 索引的优点 索引优化 B-Tree 和 B+Tree 原理 四、查询性能优化 五、切分 垂直切分 水平切分...

丁典
55分钟前
1
0
rsync通过同步服务、系统日志、screen工具

rsync通过后台服务同步 在远程主机中建立一个rsync服务器,在服务器上配置好rsync的各种应用,然后将本机作为rsync的一个客户端连接远程的rsync服务器。 首先在A机器上建立并且配置rsync的配...

黄昏残影
今天
5
0
Spring Cloud Gateway 接口文档聚合实现

在微服务架构下,通常每个微服务都会使用Swagger来管理我们的接口文档,当微服务越来越多,接口查找管理无形中要浪费我们不少时间,毕竟懒是程序员的美德。 由于swagger2暂时不支持webflux 走...

冷冷gg
今天
150
2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部