javaee论坛

普通会员

225648

帖子

343

回复

357

积分

楼主
发表于 2019-11-03 06:34:16 | 查看: 503 | 回复: 1

    梳理下,针对java工程的监控与对应的调优.主要分为两部分,第一部分是基础知识预备(这样才有思路去调优啊~),第二步是是监控工具的使用(针对不同的监控对象,会选择一个监控工具).另外欢迎骚扰:cuiyaonan2000@163.com

 

预备知识Java内存模型

先看下java内存模型,后面可以针对性的看到每个东西在该模型中的位置

 栈和堆

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存. 堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用(另外栈中保存了对象的应用地址,可以说应用或者叫指针是放在栈里的.)。

    在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

    堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

    引用变量是普通变量,定义时在栈中分配内存(即指针是在栈中),引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因,实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针! 

另:

    在栈中,会为每一个线程创建一个栈。线程越多,栈的内存使用越大。对于每一个线程栈。当一个方法在线程中执行的时候,会在线程栈中创建一个栈帧(stackframe),用于存放该方法的上下文(局部变量表、操作数栈、方法返回地址等等)。每一个方法从调用到执行完毕的过程,就是对应着一个栈帧入栈出栈的过程。

 

程序计数器

如同其名称一样。程序计数器用于记录某个线程下次执行指令位置。程序计数器也是线程私有的。

 

JVM如何判断是否为垃圾空间

    JVM如何判断一个对堆空间是否为垃圾空间的方法如下,

引用计数

    在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

    这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。

可达分析(Java采用的是类似于树形结构的可达性分析法来判断对象是否还存在引用)

    为了解决这个问题,在Java中采取了可达性分析法。该方法的基本思想是通过一系列的“gcRoots”对象作为起点进行搜索,如果在“GCRoots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

 

常用的垃圾回收算法

    如同java判断是否为垃圾的方法有多重,关于垃圾回收的算法也有多重.并且他们可能在同一个厂家的jvm中的不同部分同时在使用

 

标记-清除(Mark-Sweep)

这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的.顾名思义分为两个阶段,

第一阶段标记,标记阶段的任务是标记出所有需要被回收的对象.第二阶段清除,清除阶段就是回收被标记的对象所占用的空间(太简单了有没有~~~)

如下图,灰色的就是被标记的,然后被清空了.但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作

 

复制(Copying)

    为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来.此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

 

 

标记-整理(Mark-Compact)

为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:

 

 

分代收集(GenerationalCollecting)

    分代收集算法是目前大部分JVM的垃圾收集器采用的算法()。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为如下的3个划分.

老年代(TenuredGeneration):老年代的特点是每次垃圾收集时只有少量对象需要被回收新生代(YoungGeneration):  新生代的特点是每次垃圾回收时都有大量的对象需要被回收持久代(PermanentGeneration): 其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

 

新生代,老年代,持久带详解

先来个整体框架图便于理解,然后对照入座理解就行了

 新生代

    所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。新生代又分为如下2类区域:

Eden区:大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(一般是两个中的一个,同时Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。)Survivor区:当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“老老区(Tenured)(这里就用到了复制(Copying)算法)

 

老年代

  当新生代中的对象经过若干轮gc后还存活/或survisor在gc内存不够的时候(这就是个调优的方向哈,比如增大survivor的内存空间)。会把当前对象移动到老年代(这里其实还有个阈值默认的是15次,如果对象15次内还在survivior就会放到来年代.)。老年代一般gc策略为标记-整理(mark-compact)算法。

 

持久代

持久代一般可以不参与gc。应为其中保存的是一些代码/常量数据/类信息。JDK1.8中已经不存在持久带

 

关于垃圾回收的分类(针对堆内存)

 

MinorGC

当Eden区被对象填满时,就会执行MinorGC。并把所有存活下来的对象转移到其中一个survivor区。MinorGC同样会检查存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的(默认是15)。

 MajorGC

老年代的垃圾收集叫做MajorGC,MajorGC通常是跟fullGC是等价的,收集整个GC堆。

 

分代GC

分代GC并不收集整个GC堆的模式,而是只专注分代收集

YoungGC:只收集年轻代的GC

OldGC:只收集年老代的GC(只有CMS的concurrentcollection是这个模式)

MixedGC:收集整个younggen以及部分oldgen的GC(只有G1有这个模式)

 FullGC

FullGC定义是相对明确的,就是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代permgen)的全局范围的GC。

 JVM参数调优方案

应该必变FullGC因为这样会很影响效率,还有就是避免更多的对象到老年代(特别注意新生代内不能不够就会放到老年代的问题.).

所以新生代应该尽可能的大,避免进入老年代.同时新生代的survivor不能太小.否则也会进入老年代.同时可以将对象的手机次数增加,以提高进入老年代的门槛. 反正就是个度的问题.网上其实有很多经验分享,但是目前先写这些,另欢迎骚扰cuiyaonan2000@163.com

堆的初始值与堆的最大可用值相等

初始值越小,垃圾回收的次数就越多

-Xms:堆初始值-Xmx:堆最大可用值

 

配置新生代和老年代的调优参数配置

-XX:survivorRatio=2Eden是from区(s0)或者to区(s1)的两倍.默认的,Eden:from:to=8:1:1-XX:NewRatio=2设置老年区的内存大小为新生代的两倍

 

 

 

 

 

Jconsole使用

待续~~~~~~~~~~~~~~~~~~~~~~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


普通会员

2

帖子

316

回复

329

积分
沙发
发表于 2023-01-28 18:43:09

如果这就是爱,再转身的时候就该留下

您需要登录后才可以回帖 登录 | 立即注册

触屏版| 电脑版

技术支持 历史网 V2.0 © 2016-2017