JVM调优初识

JVM调优主要是针对内存管理方面的调优,包括控制各个代的大小,GC策略。
由于GC开始垃圾回收时会挂起应用线程,严重影响了性能,
调优的目是为了尽量降低GC所导致的应用线程暂停时间、 减少Full GC次数。

代大小调优
最关键参数:-Xms、 -Xmx 、-Xmn 、-XX:SurvivorRatio、-XX:MaxTenuringThreshold、-XX:PermSize、-XX:MaxPermSize

-Xms、 -Xmx 通常设置为相同的值,避免运行时要不断扩展JVM内存,这个值决定了JVM heap所能使用的最大内存。

-Xmn 决定了新生代空间的大小,新生代Eden、S0、S1三个区域的比率
可以通过-XX:SurvivorRatio来控制(假如值为 4  表示:Eden:S0:S1 = 4:3:3 )

-XX:MaxTenuringThreshold 控制对象在经过多少次
minor GC之后进入老年代,此参数只有在Serial 串行GC时有效。

-XX:PermSize、-XX:MaxPermSize 用来控制方法区的大小,通常设置为相同的值。

1.避免新生代大小设置过小

当新生代设置过小时,会产生两种比较明显的现象,一是minor GC次数频繁,
二是可能导致 minor GC对象直接进入老年代。当老年代内存不足时,会触发Full GC。

2.避免新生代设置过大
新生代设置过大,会带来两个问题:一是老年大变小,可能导致Full  GC频繁执行;
二是 minor GC 执行回收的时间大幅度增加。

3.避免Survivor区过大或过小

-XX:SurvivorRatio参数的值越大,就意味着Eden区域变大,
minor GC次数会降低,但两块Survivor区域变小,
如果超过Survivor区域内存大小的对象在minor GC后仍没被回收,则会直接进入老年代,

-XX:SurvivorRatio参数值设置过小,就意味着Eden区域变小,
minor GC触发次数会增加,Survivor区域变大,
意味着可以存储更多在minor GC后任存活的对象,避免其进入老年代。

4.合理设置对象在新生代存活的周期

新生代存活周期的值决定了新生代对象在经过多少次Minor GC后进入老年代。
因此这个值要根据自己的应用来调优,
Jvm参数上这个值对应的为-XX:MaxTenuringThreshold,默认值为15次。

面试题:
1. 内存模型以及分区,需要详细到每个区放什么。

1:主内存,工作内存,主内存存储对象的变量,各个线程修改变量都在工作内存中实现。
线程之间不能跨过主内存去操作另一个线程的工作内存。必须要通过主内存。
2:运行时数据区域:虚拟机栈,本地方法栈,程序计数器,堆,方法区
① 程序计数器:线程私有的,他是一块较小的内存空间,
他相当字节码于解释器中的指针,也就是该内存存放下一条即将执行指令的地址。
字节码解释器就是通过改变 这个计数器的值来选择下一条即将执行的指令。
每一个线程都有一个程序计数器(内存),这样线程切换的时候就能找到自己各个线程各自即将执行的下一条指令
 所以说是线程私有的。在JVM规范中规定,如果线程执行的是非native方法,
 则程序计数器中保存的是当前需要执行的指令的地址;
 如果线程执行的是native方法,则程序计数器中的值是undefined。
 
②java虚拟机栈:线程私有的,每一个方法在执行的时候就会创建一个栈帧来存放方法的局部变量,
操作数栈,返回地址等,当方法执行完成的时候就释放该栈帧。
栈帧:虚拟机栈中是一栈帧为单位存储的,所以一个虚拟机栈中有很多栈帧,
每一个栈帧中分为:局部变量区(存放方法的参数和局部变量),操作数栈,方法的返回地址
,动态链接(一般解析解阶段是将部分符号引用转换成直接应用(类加载),
而动态链接是另外一部分的符号引用转换成直接引用(运行时))

③本地方法栈:线程私有,本地方法指的是那种不是用java语言写的方法,
java虚拟机栈只针java方法,而不是本地方法。hotspot虚拟机支持别的语言写的方法在虚拟机上运行,
本法方法栈和java虚拟机栈一样。只是他们服务的对象不一样而已,一个为java方法服务,一个为native方法服务。

④java堆:线程共享的,不过也可能为多个线程分配私有的buffer,
也就是每个线程有自己的缓存器,java堆可以是物理上连续的,也可以是不连续的。
java堆是垃圾回收器管理的主要区域,所以也叫gc堆。java堆可以分为:新生代和老年代

⑤方法区:线程共享的,可以理解为gcc中所所说的静态区,不过也不是确切的准确,
因为在hotspot虚拟机中他存放的是类中静态变量和常量(注意是常量哦)。
因为他能存储常量,所以还有存储常量的区域有一个特别的名称,叫做常量池
(包括引用和基本数据类型的常量),方法区并不是堆,这一点和静态区很相似。
所以别名叫non-heap,java堆中可以选择不实现gc回收,但是实际上呢还是会的,
只能说垃圾回收器在这个区域不活跃而已,但是回收都是回收常量池中的常量,而不是静态变量。可以称为永久代。

⑥运行时常量池:他是方法区的一部分,但是和方法区的常量池有区别,
他存放的常量是在运行时产生的,而不是编译时产生的。注意与普通方法区的区别

2. 堆里面的分区:Eden,survival from to,老年代,各自的特点。
1:Eden区的对象都是朝生夕死,发生minor gc的时候会清除eden区和survival区的,
把存活的对象移到另一个Survival区,该survial区由老年代保证。
当在年轻代中对象经过多次minor gc以后还存活,达到老年代的年纪,
就会移动到老年代,还有就是大对象在年轻代无法存储,直接转到老年代,还有可能因为担保而进入老年代的
    Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,
因此在堆上分配内存需要加锁。而Sun JDK为提升效率,
会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,
这块空间称为TLAB(Thread Local Allocation Buffer)。
在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。
如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。
2.Survival from to
    Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,
一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。
在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。
Survival to区会把一些存活得足够旧的对象移至年老代。
3.年老代
    年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。
当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源

3. 对象创建方法,对象的内存分配,对象的访问定位。

1对象的创建包括三步骤:
①当遇到new命令的时候,会在常量池中检查该对象的符号引用是否存在,不存在则进行类的加载,
否则执行下一步
②分配内存,将将要分配的内存都清零。
③虚拟机进行必要的设置,如设置hashcode,gc的分代年龄等
,此时会执行<init>命令在执行之前所有的字段都为0,执行<init>指令以后,安装程序的意愿进行初始化字段。

2:对象的内存分配:包括对象头,实例数据,对齐填充
①对象头:包括对象的hascode,gc分代年龄,锁状态标等。
②实例数据:也就是初始化以后的对象的字段的内容,包括父类中的字段等
③对齐填充:对象的地址是8字节,虚拟机要求对象的大小是对象的整数倍(1倍或者两倍)。因此就会有空白区。

3:对象的访问:hotspan中 是采用对象直接指向对象地址的方式(这样的方式访问比较快)
(还有一种方式就是句柄,也就是建一张表维护各个指向各个地址的指针,然后给指针设置一个句柄 (别名),
然后引用直接指向这个别名,就可以获得该对象,这种的优势就是,
实例对象地址改变了,只要修改句柄池中的指针就可以了,而不用引用本身不会发生 改变)。


 


4. GC的两种判定方法:引用计数与引用链。
1:引用计数:给一个对象设置一个计数器,当被引用一次就加1,当引用失效的时候就减1,
如果该对象长时间保持为0值,则该对象将被标记为回收。

优点:算法简单,效率高,缺点:很难解决对象之间的相互循环引用问题。

2:引用链(可达性分析):现在主流的gc都采用可达性分析算法来判断对象是否已经死亡。
可达性分析:通过一系列成为GC Roots的对象作为起点,从这些起点向下搜索,
搜索所走过的路径成为引用链,当一个对象到引用链没有相连时,则判断该对象已经死亡。

3:可作为gc roots的对象:虚拟机栈(本地方法表)中引用的对象(因为在栈内,被线程引用),
方法区中类静态属性引用的对象,方法区中常量引用的(常量存放在常量池中,常量池是方法区的一部分)对象,
native方法引用的对象

4:引用计数和引用链是只是用来标记,判断一个对象是否失效,而不是用来清除。

5. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,
分别用在什么地方,如果让你优化收集方法,有什么思路?

1:标记清除:直接将要回收的对象标记,发送gc的时候直接回收:
特点回收特别快,但是回收以后会造成很多不连续的内存空间,
因此适合在老年代进行回收,CMS(current mark-sweep),就是采用这种方法来会后老年代的。

2:标记整理:就是将要回收的对象移动到一端,然后再进行回收,
特点:回收以后的空间连续,缺点:整理要花一定的时间,适合老年代进行会后,
parallel Old(针对parallel scanvange gc的) gc和Serial old就是采用该算法进行回收的。

3:复制算法:将内存划分成原始的是相等的两部分,每次只使用一部分,
这部分用完了,就将还存活的对象复制到另一块内存,将要回收的内存全部清除
。这样只要进行少量的赋值就能够完成收集。比较适合很多对象的回收,
同时还有老年代对其进行担保。(serial new和parallel new和parallel scanvage)

优化收集方法:对复制算法的优化:并不是将两块内存分配同等大小,
可以将存活率低的区域大一些,而让回收后存活的对象所占的区域小一些,
不够的内存由老年代的内存来保证,这样复制算法的空闲的空间减少了。

两个survival区域的是为了减少风险率,有一个survivor区要参与回收,
也要参与存储,只要只有10%的空间浪费,同时也减少对老年代的依赖。

6. GC收集器有哪些?CMS收集器与G1收集器的特点。
1:串行的,也就是采用单线程(比较老了),
分类:serial new(收集年轻代,复制算法)和serial old(收集老年代,标记整理),
缺点:单线程,进行垃圾回收时暂时所有的用户线程。优点:实现简单。

2:并行的,采用多线程,对于年轻代有两个: parallel new(简称ParNew)(参考serial new的多线程版本)
和parallel scavenge;parallel scavenge是一个针对年轻代的垃圾回收器,
采用复制算法,主要的优点是进行垃圾回收时不会停止用户线程(不会发生stop all world)

老年代回收器也有两种:Parallel old是parallel scavenge的我老年代设计的。
CMS(并发标记清除),他采用标记清除算法,采用这种的优点就是快咯,因此会尽快的进行回收,减少停顿时间。

3:高级杀手:G1收集器,年轻代和老年代通吃,最新一代的技术。
面向服务器端的垃圾收集器(并行+并发的垃圾收集器)。

7. Minor GC与Full GC分别在什么时候发生?

1.Minor GC发生:当jvm无法为新的对象分配空间的时候就会发生minor gc,
所以分配对象的频率越高,也就越容易发生minor gc。

2.Full GC:发生GC有两种情况,①当老年代无法分配内存的时候,会导致MinorGC,
②当发生Minor GC的时候可能触发Full GC,由于老年代要对年轻代进行担保,
由于进行一次垃圾回收之前是无法确定有多少对象存活,
因此老年代并不能清除自己要担保多少空间,因此采取采用动态估算的方法:
也就是上一次回收发送时晋升到老年代的对象容量的平均值作为经验值,
这样就会有一个问题,当发生一次Minor GC以后,存活的对象剧增(假设小对象)
,此时老年代并没有满,但是此时平均值增加了,会造成发生Full GC

8. 几种常用的内存调试工具:jmap、jstack、jconsole。

9. 类加载的五个过程:加载、验证、准备、解析、初始化。

1:加载:加载有两种情况,①当遇到new关键字,或者static关键字的时候就会发生
(他们对应着对应的指令)如果在常量池中找不到对应符号引用时,就会发生加载 ,
②动态加载,当用反射方法(如class.forName(“类名”)),如果发现没有初始化,则要进行初始化。
(注:加载的时候发现父类没有被加载,则要先加载父类)

2:验证:这一阶段的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,
并不会危害虚拟机自身的安全(虽然编译器会严格的检查java代码并生成class文件,
但是class文件不一定都是通过编译器编译,然后加载进来的,
因为虚拟机获取class文件字节流的方式有可能是从网络上来的,
者难免不会存在有人恶意修改而造成系统崩溃的问题,
class文件其实也可以手写16进制,因此这是必要的)

3:准备:该阶段就是为对象分派内存空间,然后初始化类中的属性变量,
但是该初始化只是按照系统的意愿进行初始化,也就是初始化时都为0或者为null。
因此该阶段的初始化和我们常说初始化阶段的初始化时不一样的

4:解析:解析就是虚拟机将常量池中的符号引用替换成直接引用的过程。
符号引用其实就是class文件常量池中的各种引用,
他们按照一定规律指向了对应的类名,或者字段,但是并没有在内存中分配空间,
因此符号因此就理解为一个标示,而在直接引用直接指向内存中的地址。

5:初始化:简单讲就是执行对象的构造函数,给类的静态字段按照程序的意愿进行初始化,注意初始化的顺序。
(此处的初始化由两个函数完成,一个是<clinit>,初始化所有的类变量(静态变量),
该函数不会初始化父类变量,还有一个是实例初始化函数<init>,
对类中实例对象进行初始化,此时要如果有需要,是要初始化父类的)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值