Java内存管理

霸气de小男生 提交于 2019-11-27 19:23:08

Java内存管理

Java运行时内存区
在这里插入图片描述
其中,对于这各个部分有一些是线程私有的,其他则是线程共享的。

线程私有的如下:
程序计数器当前线程所执行的字节码的行号指示器

Java虚拟机栈Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。
每个线程都有自己独立的栈空间
线程栈只存基本类型和对象地址
方法中局部变量在线程空间中

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

线程共享的如下:
Java堆存放对象实例,几乎所有的对象实例以及其属性都在这里分配内存。

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

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

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

对象访问
Java是面向对象的一种编程语言,那么如何通过引用来访问对象呢?一般有两种方式:
1.通过句柄访问
在这里插入图片描述
2.直接指针(此种方式也是HotSpot虚拟机采用的方式)
在这里插入图片描述
内存溢出
在JVM申请内存的过程中,会遇到无法申请到足够内存,从而导致内存溢出的情况。一般有以下几种情况:
虚拟机栈和本地方法栈溢出
StackOverflowError: 线程请求的栈深度大于虚拟机所允许的最大深度(循环递归)
OutOfMemoryError: 虚拟机在扩展栈是无法申请到足够的内存空间,一般可以通过不停地创建线程引起此种情况

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

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

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

垃圾收集
理论基础
在通常情况下,我们掌握java的内存管理就是为了应对网站/服务访问慢,慢的原因一般有以下几点:
内存:垃圾收集占用cpu;放入了太多数据,造成内存泄露线程死锁
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
1.内存消耗:copy需要两倍的最大live set内存;mark-sweep则只需要一倍。
2.效率上:copy与live set成线性相关,效率高;mark-sweep则与堆大小线性相关,效率较低。

分代收集
分代收集是目前比较先进的垃圾回收方案。有以下几个相关理论
分代假设:大部分对象的寿命很短,“朝生夕死”,重点放在对年青代对象的收集,而且年青代通常只占整个空间的一小部分。
把年青代里活的很长的对象移动到老年代。
只有当老年代满了才去收集。
收集效率明显比不分代高。

HotSpot虚拟机的分代收集,分为一个Eden区、两个Survivor去以及Old Generation/Tenured区,其中Eden以及Survivor共同组成New Generatiton/Young space。通常将对New Generation进行的回收称为Minor GC;对Old Generation进行的回收称为Major GC,但由于Major GC除并发GC外均需对整个堆以及Permanent Generation进行扫描和回收,因此又称为Full GC。
在这里插入图片描述
Eden区是分配对象的区域。
Survivor是minor/younger gc后存储存活对象的区域。
Tenured区域存储长时间存活的对象。

分代收集中典型的垃圾收集算法组合描述如下:
年青代通常使用Copy算法收集,会stop the world
老年代收集一般采用Mark-sweep-compact, 有可能会stop the world,也可以是concurrent或者部分concurrent。

那么何时进行Minor GC、何时进行Major GC? 一般的过程如下:
对象在Eden Space完成内存分配
当Eden Space满了,再创建对象,会因为申请不到空间,触发Minor GC,进行New(Eden + S0 或 Eden S1) Generation进行垃圾回收
Minor GC时,Eden Space不能被回收的对象被放入到空的Survivor(S0或S1,Eden肯定会被清空),另一个Survivor里不能被GC回收的对象也会被放入这个Survivor,始终保证一个Survivor是空的
在Step3时,如果发现Survivor区满了,则这些对象被copy到old区,或者Survivor并没有满,但是有些对象已经足够Old,也被放入Old Space。
当Old Space被放满之后,进行Full GC

但这个具体还要看JVM是采用的哪种GC方案。
New Generation的GC有以下三种:
Serial
ParallelScavenge
ParNew

对于上述三种GC方案均是在Eden Space分配不下时,触发GC。
Old Generation的GC有以下四种:
Serial Old
Parallel
CMS

对于Serial Old, Parallel Old而言触发机制为
Old Generation空间不足
Permanent Generation空间不足
Minor GC时的悲观策略
Minor GC后在Eden上分配内存仍然失败
执行Heap Dump时
外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止

对于CMS而言触发机制为:
当Old Generation空间使用到一定比率时触发;HopSpot V1.6中默认是92%,可通过PrintCMSInitiationStatistics(此参数在V1.5中不能用)来查看这个值到底是多少;可通过CMSInitiatingOccupancyFaction来强制指定,默认值并不是复制在这个值上,是根据如下公式计算出来的:((100 -MinHeapFreeRatio) +(double)(CMSTriggerRatio* MinHeapFreeRatio) / 100.0)/ 100.0;MinHeapFreeRatio默认值:40 CMSTriggerRatio默认值:80

当Permanent Generation采用CMS收集且空间使用到一定比率触发;Permanent Generation采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled Hotspot V1.6中默认为92%;可通过CMSInitiatingPermOccupancyFraction来强制指定,同样,它是根据如下公式计算出来的:((100 -MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio) / 100.0)/ 100.0;MinHeapFreeRatio默认值:40 CMSTriggerPermRatio默认值:80

Hotspot根据成本计算决定是否需要执行CMS GC;可通过-XX:+UseCmsInitiatingOccupancyOnly来去掉这个动态执行的策略。
外部调用System.gc,且设置了ExplicitGCIInvokesConcurrent;需要注意,在hotspot 6中,在这种情况下如果应用同时使用了NIO,可能会出现bug。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!