java垃圾回收机制优化_JVM性能优化--Java的垃圾回收机制

一、Java内存结构

4c620bf019b49e1ca1e8c734dea8ed8a.png

1、Java堆(Java Heap)

java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。

java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”(Garbage Collected Heap)。从内存回收角度来看java堆可分为:新生代和老生代。从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。

根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

2、Java虚拟机栈(Java Virtual Machine Stacks)

java虚拟机也是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。局部变量表存放了编译期可知的各种基本数据类型(8个基本数据类型)、对象引用(地址指针)、returnAddress类型。

局部变量表所需的内存空间在编译期间完成分配。在运行期间不会改变局部变量表的大小。

这个区域规定了两种异常状态:如果线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,在扩展是无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3、本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈所发挥作用非常相似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务。本地方法栈也是抛出两个异常。

4、方法区(Method Area)

方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

5、直接内存(Direct Memory)

直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但这部分区域也呗频繁使用,而且也可能导致OutOfMemoryError异常

在JDK1.4中新加入的NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

6、运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在加载后进入方法区的运行时常量池中存放。

## 7、程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。

8、执行引擎

虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。

9、垃圾收集系统

垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理

二、垃圾回收机制算法分析

1、什么是垃圾回收机制

不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。

public class Test {

public static void main(String[] args) {

Test test = new Test();

test = null;

System.gc(); // 手动回收垃圾

}

@Override

protected void finalize() throws Throwable {

// gc回收垃圾之前调用

System.out.println("垃圾回收机制...");

}

}

2、finalize方法作用

Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

3、新生代与老年代

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。

这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

堆的内存模型大致为:

ebb1bb40b7acdf4261f6438e9ff865ba.png

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。

默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为

新生代和老年代。其中新生带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。

新生代分为den区、s0区、s1区,s0和s1也被称为from和to区域,他们是两块大小相等并且可以互相角色的空间。

绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次

新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后,则进入老年代。

三、如何判断对象是否存活

1、引用计数法

概念

引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。

首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。

什么是引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。

2、根搜索算法

概念

根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。

那么问题又来了,如何选取GCRoots对象呢?在Java语言中,可以作为GCRoots的对象包括下面几种:

(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

(2). 方法区中的类静态属性引用的对象。

(3). 方法区中常量引用的对象。

(4). 本地方法栈中JNI(Native方法)引用的对象。

下面给出一个GCRoots的例子,如下图,为GCRoots的引用链。

2137b033956902b013f68abb7e0db311.png

265ae32cb9a74b5662f40740a5d7ed18.png

user3和user5没有引用链

根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

四、垃圾回收机制策略

1、标记清除算法

概念

该算法有两个阶段。

标记阶段:找到所有可访问的对象,做个标记

清除阶段:遍历堆,把未被标记的对象回收

应用场景

该算法一般应用于老年代,因为老年代的对象生命周期比较长。

优缺点

标记清除算法的优点和缺点

1.优点

是可以解决循环引用的问题

必要时才回收(内存不足时)

2.缺点:

回收时,应用需要挂起,也就是stop the world。

标记和清除的效率不高,尤其是要扫描的对象比较多的时候

会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到),

2、复制算法

概念

如果jvm使用了coping算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。

应用场景

coping算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用coping算法进行拷贝时效率比较高。jvm将Heap 内存划分为新生代与老年代,又将新生代划分为Eden(伊甸园) 与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 以及From Survivor Space 与To Survivor Space 之间实行Copying 算法。 不过jvm在应用coping算法时,并不是把内存按照1:1来划分的,这样太浪费内存空间了。一般的jvm都是8:1。也即是说,Eden区:From区:To区域的比例是

始终有90%的空间是可以用来创建对象的,而剩下的10%用来存放回收后存活的对象。

c378bdb3165f190660c27669a9279115.png

1、当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。

2、当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。

3、可见部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代

注意: 万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间。

优缺点

优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。

缺点: 会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,coping的性能会变得很差。

3、标记压缩算法

标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化

概念

b811a54b706c27f47281fee24de444d6.png

压缩算法简单介绍

任意顺序 : 即不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动对象;

线性顺序 : 考虑对象的引用关系,例如a对象引用了b对象,则尽可能将a和b移动到一块;

滑动顺序 : 按照对象原来在堆中的顺序滑动到堆的一端。

优缺点

优点:解决内存碎片问题,

缺点:压缩阶段,由于移动了可用对象,需要去更新引用。

4、Minor GC和Full GC区别

概念:

新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具

备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

老年代 GC(Major GC  / Full GC):指发生在老年代的 GC,出现了 Major GC,经常

会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里

就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10

倍以上。

Minor GC触发机制:

当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GCFull GC触发机制:

当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代,当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载其中

Minor GC如下图所示

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold (阈值)来设置。

ccf6edf2b75ba916ba901717aaa03fa2.png

JVM的永久代中会发生垃圾回收么?

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区

(注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

5、分代算法

概述

这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。可以用抓重点的思路来理解这个算法。

新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。

新生代

在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;

老年代

而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须“标记-清除-压缩”算法进行回收。参看java垃圾回收算法之-标记_清除压缩

新创建的对象被分配在新生代,如果对象经过几次回收后仍然存活,那么就把这个对象划分到老年代。

老年代区存放Young区Survivor满后触发minor GC后仍然存活的对象,当Eden区满后会将存活的对象放入Survivor区域,如果Survivor区存不下这些对象,GC收集器就会将这些对象直接存放到Old区中,如果Survivor区中的对象足够老,也直接存放到Old区中。如果Old区满了,将会触发Full GC回收整个堆内存。

个人博客 蜗牛

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/359856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《linux 网卡别名的添加和绑定》RHEL6

网卡别名的配置: 这个和ifconfig临时修改网卡ip 差不多,但是不一样。都是临时的,只要重启电脑就没了。 配永久的ip别名: cp ifcfg-eth0 ifcfg-eth0:0 vim ifcfg-eth0:0 这样做也能出来,对不对就不知道了 重启网络就ok…

NGUI中UILabel使用url标签的一个bug

在NGUI里,UILabel控件可以支持一些简单功能的标签,使文本显示更丰富及实现类似超链接的功能。但是在使用的时候发现了NGUI3.5.9版本里存在着一个bug。不过还好修复这个bug也很简单。 在UILabel中支持[urllink]text[/url]的方式来定义类超链接的文本。bug…

web前端模块化开发_真正的模块化Web应用程序:为什么没有开发标准?

web前端模块化开发OSGI , SpringSource , Jboss模块 ,J2EE和清单永远不会结束。所有这些技术都向他们的最终用户/开发人员保证了相同的东西,或多或少是Java模块化Web应用程序(?)。 但是&#xf…

[转]Oracle DB管理内存

• 描述SGA 中的内存组件• 实施自动内存管理• 手动配置SGA 参数• 配置自动PGA 内存管理内存管理:概览DBA 必须将内存管理视为其工作中至关重要的部分,因为:• 可用内存空间量有限• 为某些类型的功能分配更多内存可提高整体性能• 自动优化…

java request获取文件_request获取路径方式

从request获取各种路径总结request.getRealPath("url"); // 虚拟目录映射为实际目录request.getRealPath("./"); // 网页所在的目录request.getRealPath("../"); // 网页所在目录的上一层目录request.getContextPath(); // 应用的web目录的…

servlet 3.0异步_Servlet 3.0异步处理可将服务器吞吐量提高十倍

servlet 3.0异步Servlet是Java中处理服务器端逻辑的主要组件,新的3.0规范引入了一些非常有趣的功能,其中异步处理是最重要的功能之一。 可以利用异步处理来开发高度可伸缩的Web应用程序。 使用此功能可以有效地构建Web 2.0站点和AJAX应用程序。 我们的JC…

Android自定义进度条-带文本(文字进度)的水平进度条(ProgressBar)

/** * 带文本提示的进度条 */ public class TextProgressBar extends ProgressBar { private String text; private Paint mPaint; public TextProgressBar(Context context) { super(context); initText(); } public TextProgressBar(Context context, AttributeSet attrs, in…

java 获取服务器网络名_java-siger java使用siger 获取服务器硬件信息(CPU 内存 网络 io等) - 下载 - 搜珍网...

java读取系统信息/java读取系统信息/hyperic-sigar-1.6.4/java读取系统信息/hyperic-sigar-1.6.4.zipjava读取系统信息/hyperic-sigar-1.6.4/AUTHORSjava读取系统信息/hyperic-sigar-1.6.4/bindings/java读取系统信息/hyperic-sigar-1.6.4/bindings/dotnet/java读取系统信息/hy…

《无码的青春》第四章 程序员的二象性,左手流氓,右手疯子

“道哥,你都30了,怎么还不考虑成家的事情啊”,张小凡问到。 其实他不了解道哥的过去,当年道哥搞javaABC论坛的时候,有一个javaABC官方扯淡群,里面聚集了一群程序员,从不讨论技术,唯…

java导出highcharts_Highcharts导出代码Java版

Highcharts是一个用纯JavaScript编写的图表库,提供了一个交互式的图表添加到您的网站或Web应用程序的简单方法。Highcharts目前支持线,样条,面积,areaspline,柱形图,条形图,饼图和散点图类型。同…

smartgwt_SmartGWT入门,提供出色的GWT界面

smartgwtSmartGWT简介 我最近开始使用SmartGWT ,它是一个基于GWT的框架,该框架为您的应用程序UI提供了一个全面的小部件库,并为服务器端的数据管理提供了帮助。 您可以在SmartGWT展示柜上查看其漂亮的功能。 我准备了一个简短的“入门”指南…

java录入会员信息_java-第三章-升级我行我素购物管理系统,实现会员信息录入的功能...

import java.util.Scanner;public class A01 {/*** param args*/public static void main(String[] args) {// TODO Auto-generated method stubScanner input new Scanner (System.in);System.out.println("我行我素购物管理>客户信息管理>添加客户信息");Sy…

Beaglebone Back学习五(PWM测试)

PWM测试 参考链接 1 Enable PWM on BeagleBone with Device Tree overlays 2 Using PWM on the Beaglebone Black 3 Beaglebone Coding 101: Buttons and PWM 4 Using PWM outputs 5 beaglebone-black-cpp-PWM 6 Enabling PWM Support in the kernel 7转载于:https://www.cnblo…

CUBA平台的理念

最近发生了很多事。 在CUBA于6月1日正式发布之后,我们推出了一个新版本,在一些Java网站上发布了我们的第一篇文章,并在伦敦的Devoxx UK会议上介绍了该平台 。 但是在热潮继续之前,大约是时候阐明CUBA背后的哲学了。 与企业软件开…

mysql orderby多个_MySQL OrderBy

MySQL会为每个线程分配一个内存(sort_buffer)用于排序,该内存小大为 sort_buffer_size如果排序的数量小于 sort_buffer_size,排序将会在内存中完成。如果排序数据量很大,内存中无法存下这么多数据,则会使用磁盘临时文件来辅助排序…

java 双重检查锁_Java中可怕的双重检查锁定习惯用法

java 双重检查锁本文讨论的问题不是新问题,但即使是经验丰富的开发人员也仍然很棘手。 单例模式是常见的编程习惯用法。 但是,当与多个线程一起使用时,必须进行某种类型的同步,以免破坏代码。 在相关文章中,我们的JCG合…

mysql-bin.index找不到_MySQL不能启动 mysql-bin.index' not found (Errcode: 13)

配置复制,添加如下内容到/etc/my.cnf:log-bin/var/lib/mysql/binlogs/mysql-binmax_binlog_size100Mexpire_logs_days5sync_binlog1binlog_cache_size1Mbinlog-formatROW结果重启的时候,报错:-[root www.linuxidc.com mysql]# /et…

[ZZ] 使用rsync来实现快速删除大量文件

昨天遇到了要在Linux下删除海量文件的情况,需要删除数十万个文件。这个是之前的程序写的日志,增长很快,而且没什么用。这个时候,我们常用的删除命令rm -fr * 就不好用了,因为要等待的时间太长。所以必须要采取一些非常…

java 文件缓冲区_Java开发笔记(八十六)通过缓冲区读写文件

前面介绍了利用文件写入器和文件读取器来读写文件,因为FileWriter与FileReader读写的数据以字符为单位,所以这种读写文件的方式被称作“字符流I/O”,其中字母I代表输入Input,字母O代表输出Output。可是FileWriter的读操作并不高效…

Scramble String -- LeetCode

原题链接: http://oj.leetcode.com/problems/scramble-string/这道题看起来是比較复杂的,假设用brute force,每次做分割,然后递归求解,是一个非多项式的复杂度,一般来说这不是面试官想要的答案。这事实上是一道三维动态…