JVM学习笔记大量例子保证学会.docx
- 文档编号:23694560
- 上传时间:2023-05-20
- 格式:DOCX
- 页数:46
- 大小:2.94MB
JVM学习笔记大量例子保证学会.docx
《JVM学习笔记大量例子保证学会.docx》由会员分享,可在线阅读,更多相关《JVM学习笔记大量例子保证学会.docx(46页珍藏版)》请在冰豆网上搜索。
JVM学习笔记大量例子保证学会
目录
什么是Java虚拟机?
1
虚拟机的生命周期?
1
程序计数器是什么?
2
什么是Java虚拟机栈?
2
什么是栈深度?
2
Java虚拟机栈GC内存测试8
Java堆内存调优17
持久代的设置23
线程栈的设置25
JVM垃圾回收器及测试28
性能调优35
Tomcat调优实践40
网络摘抄:
45
什么是Java虚拟机?
Java虚拟机就是就是运行java程序的一个实例,就和电脑的虚拟机一样有自己的内存空间,CUP等,一个运行时的java虚拟机实例的天职就是负责运行一个java程序,在启动一个java程序的同时也就会诞生一个虚拟机的实例,当java程序退出,虚拟机的实例就随之消亡,比如说,如果在同一台电脑上面同时运行了3个java程序,就会得到3个java虚拟机的实例,每个java程序都运行在它自己的虚拟机中。
Java虚拟机内部有2个线程,守护线程和非守护线程。
守护线程通常是由java虚拟机内部使用,如执行垃圾收集任务的gc,而java的初始线程为非守护线程(main方法)。
虚拟机的生命周期?
当非守护线程终止后,虚拟机的实例就自动退出了,也能通过System.exit方法退出。
Java虚拟机的内存模型:
1.程序计数器
2.Java虚拟机栈
3.本地方法区
4.Java堆
5.方法区
程序计数器是什么?
程序计数器是一块很小内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
字节码解释工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理等基础功能都由计数器所完成。
也就是说程序计数器就是负责程序执行字节码指令的。
什么是Java虚拟机栈?
java虚拟机栈也是线程私有的内存空间,它和java线程在同一时间创建,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
每一个方法被调用直至完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
java虚拟机规范中,定义了两种异常与栈空间相关:
StackOverflowError及OutOfMemoryError.
使用-Xss参数来设置栈的大小,栈的大小直接决定了函数调用可以达的深度
由于栈中包括局部变量表,操作数栈,返回地址等信息,方法在调用时,如果方法的参数和局部变量相对较多,那么栈中的局部变量表就越大,它的栈帧就越大。
栈深度者越浅。
什么是栈深度?
我们的很多变量都是存放在栈内存中的,栈的高度称之为栈深度,栈深度越高,深度就越深,所存放的局部变量就越多,如果栈的深度不够深,局部变量越多,就容易出现异常
StackOverflowError栈溢出异常及OutOfMemoryError(更多出现在堆内存溢出情况),如下图
测试
结果:
如果栈深度不够出现了StackOverflowError,这时候就需要通过设置-Xss进行设置栈的大小,栈的大小直接决定了函数调用可以达的深度。
由于栈中包括局部变量表,操作数栈,返回地址等信息,方法在调用时,如果方法的参数和局部变量相对较多,那么栈中的局部变量表就越大,它的栈帧就越大。
栈深度者越浅。
下图测试代码多了6个局部变量,查看栈深度。
测试
多了变量栈的深度也随之下降了。
程序设计:
尽量少使用局部变量,局部变量过多会造成栈深度变浅,导致栈溢出。
栈设置,并不是设置的越大越好,应该根据项目的实际情况进行设置。
栈中的局部变量表就越大,它的栈帧就越大。
栈深度者越浅。
如何查看栈中的变量表呢?
jclasslib工具可以查看class文件中每个方法所分配的最大局部变量表容量等。
局部变量表是以字为单位,进行内存的划分,long和double占2个字,另外每一个虚拟机都会有一个This执行变量,占1一个字。
测试:
使用jclasslib工具查看:
由上图看出,显示我们的栈深度为7,字节为13
long占两个字节,有6个Long类型占 6*2个字+1个字(This)=13个字
Java虚拟机栈GC内存测试
测试程序:
s
查看字:
为什么test1只有3个字呢?
作用域不同,即在定义b的时候a的作用域已经没有意义了,那么b就可以完全重用a变量的空间。
单定义到大括号作用域内,字还是存在的,只不过没有被重用。
b重用是因为在运行的过程中a作用域的字被gc掉了。
测试一
查看GC:
可以看出,我们分配了一个较大的空间,gc过后,好像并没有怎么回收。
测试二
查看GC
可以看到,通过用a重用b的空间,gc过后,内存已经被回收了
测试三
被GC,内存回收掉了
测试四
程序
查看GC
没有被GC
测试五:
没有被gc,只是a把c的空间重用了,并没有重用b的空间
测试六:
已经GC,两个变量被重用
测试七:
方法调用过后,可以通过gc被gc掉。
总结
局部表里中的字,在方法没有结束之前(除非被强制设置为null或者被其他的变量复盖空间)是不能GC,但是一旦方法结束过后,那么是可以被gc掉的
Java堆内存调优
什么是Java堆内存?
Java堆是Java运行时内存最重要的部分,几乎所有的对象和数组都是在堆中进行分配空间。
Java堆可以分为新生代和老年代两个部分,新生代用于存放刚刚产生的对象和年轻对象,如果对象经过几次minorGC,都没有被GC掉,生存时间足够长,新生代就会被移入到老年代中。
新生代:
包括3块区域:
Eden区,Survivor0空间和Survivor1空间
老年代只有一个区,老年代是存放时间较长,经过垃圾回收次数较多的对象。
理想情况下,老年代都是放一些声明周期很长的对象,数量应该是很少的。
比如数据库连接池。
堆空间如下图:
堆内存的GC:
当程序在堆内存中运行的时候,会出现两种GC,minorGC,FullGC
minorGC(小范围的GC)是经常会发生的事情,发生在年轻代,
FULLGC全局GC,内存不够用触发
会造成延时。
并发量过大有可能出现在JVM上面。
堆内存空间的存储。
每一次放对象的时候,都是放入eden区域,和其中一个survivor区域;另外一个survivor区域是空闲的。
当eden区域和一个survivor区域放满了以后(spark运行过程中,产生的对象实在太多了),就会触发minorgc,小型垃圾回收。
把不再使用的对象,从内存中清空,给后面新创建的对象腾出来点儿地方。
清理掉了不再使用的对象之后,那么也会将存活下来的对象(还要继续使用的),放入之前空闲的那一个survivor区域中。
这里可能会出现一个问题。
默认eden、survior1和survivor2的内存占比是8:
1:
1。
问题是,如果存活下来的对象是1.5,一个survivor区域放不下。
此时就可能通过JVM的担保机制(不同JVM版本可能对应的行为),将多余的对象,直接放入老年代了。
如果你的JVM内存不够大的话,可能导致频繁的年轻代内存满溢,频繁的进行minorgc。
频繁的minorgc会导致短时间内,有些存活的对象,多次垃圾回收都没有回收掉。
会导致这种短声明周期(其实不一定是要长期使用的)对象,年龄过大,垃圾回收次数太多还没有回收到,跑到老年代。
老年代中,可能会因为内存不足,囤积一大堆,短生命周期的,本来应该在年轻代中的,可能马上就要被回收掉的对象。
此时,可能导致老年代频繁满溢。
频繁进行fullgc(全局/全面垃圾回收)。
fullgc就会去回收老年代中的对象。
fullgc由于这个算法的设计,是针对的是,老年代中的对象数量很少,满溢进行fullgc的频率应该很少,因此采取了不太复杂,但是耗费性能和时间的垃圾回收算法。
fullgc很慢。
fullgc/minorgc,无论是快,还是慢,都会导致jvm的工作线程停止工作,stoptheworld。
简而言之,就是说,gc的时候,导致延时,停止工作。
一直等着垃圾回收结束。
这个时候,问题就来了:
1.频繁的minorGC,也会导致程序的延时,停止工作,影响性能
2.老年代囤积大量的活跃对象(短生命周期额对象),导致频繁发生Fullgc,FulleGC时间比minorGC时间长,可能导致程序长时间的停止工作。
严重影响程序的运行速度和性能。
测试:
如何设置最大堆内存?
java应用程序可以使用的最大堆可以用-Xmx参数指定,最大堆指的是新生代和老年代的大小之和的最大值。
测试:
最小堆内存的设置:
1.为什么设置和最大的堆内存还要设置最小的堆内存呢?
java程序在运行时首先会被分配-Xms指定的内存大小,并尽可能尝试在这个空间段内运行程序,确实无法满足时才会向操作系统申请更多的内存,直到内存大小达到-Xmx指定的最大内存为止,如果超过-Xmx的值,则抛出OutOfMemoryError
比如:
你设置的最小堆内存为10M,那么程序会最先在你最小的堆内存中运行,如果确实无法满足了才会申请内存,直到XMX。
超过报错。
2.如何设置最小堆内存?
java应用程序可以使用的最小堆可以用-Xms参数指定,也就是JVM启动时,所占据的操作系统内存的大小。
测试
经历了2次FullGC10次MinorGC,每次GC都会消耗一定的时间,所以需要设置最小堆内存大一些,以此减少minorGC和FullGC的次数.
通常将最小堆内存和最大堆内存设置为一样,明显的GC频次降低,而且不会出现FullGC,这样在出现大并发的情况下,就会大大节约虚拟机所花的时间。
总结
通常将最小堆内存和最大堆内存设置为一样,因为如果最小堆内存设置过小会提升minorGC的次数,甚至引发FullGC
而且JVM会试图将系统内存尽可能的限制在最小堆中,当超过内存的实际使用超过了最小堆,也会触发FullGC.
我们都知道,堆内存分为年轻代和老年代,如何设置它们的内存占比呢?
新生代的设置:
参数-Xmn用于设置新生代的大小,设置一个较大的新生代会减少老年代的大小,这个参数的堆系统性能以及GC行为有很大的影响,新生代的大小一般设置为整个堆空间的1/3.
新生代越大,GC的次数就越少。
测试:
对比在相同堆内存的情况下,新生代内存大小对gc产生的影响。
持久代的设置
简介:
持久代也叫做方法区, 方法区也是JVM内存区中非常重要的一块内存区域,与堆空间类似,它是被JVM中所有的线程共享,方法区主要保存的信息是类的元数据。
方法区中最重要的是类的类型信息,常量池,域信息,静态字段,方法信息等。
方法区也称为永久区,是一块独立于java堆的内存空间,但GC也能回收,通常通过二个方面回收
1:
对常量池回收2:
对元数据回收。
持久代(方法区)不属于堆的一部分,使用-XX:
MaxPermSize可以设置持久代的最大值,使用-XX:
PermSize可以设置持久代的初始大小。
持久代的大小决定了系统可以支持多少个类定义和多少常量。
一般来说MaxPermSize设置为64MB已经可以满足绝大部分的程序正常工作,如果依然出现溢出,可以设置为128MB。
主要用于大量的动态类生成.当需要生成大量的动态类时需要设置。
内存模型:
测试程序
线程栈的设置
线程栈是线程一块私有空间,在JVM中可以使用-Xss参数设置线程栈的大小。
-Xss设置越大线程数越小,且与堆大小有关。
当我们大量线程运行的时候,查看系统能最大承受的并发数.
测试:
对线程栈设置内存大小关系,线程栈设置越大,能承受的线程数量就越少
线程栈与堆内存的关系:
在栈不变的情况下,堆内存设置越大,线程数量越少,在这种情况下,如果需要大量的线程并发执行的时候,通常设置一个较小的堆和较小的栈内存
总结:
如果系统需要大量的线程并发执行,那么设置一个较小的堆和较小的栈,有助于提高系统所能承受的最大线程数.
栈的设置,对于不是高并发的情况下,设置栈的深度越大,只是仅仅设置了一个栈的深度,但是对于高并发的情况下,栈的设置
对于每个线程的大小是一样的,这样的话线程数量就会减少。
到底如何权衡?
通常情况下应该保证堆内存的情况下,进行设置,具体需要根据项目的实际情况进行设置
java-Xss1M-Xmx500M-Xms500MTest8 推荐这种设置方式
在保证堆内存的情况下,降低一定线程数量以保证堆内存,来保证一个平衡的状态。
本地方法栈简介:
本地方法栈和java虚拟机栈的功能很相似,java虚拟机栈用于管理java函数的调用,而本地方法栈用于管理本地方法的调用。
本地方法并不是java实现的而由C来实现。
JVM垃圾回收器及测试
什么是垃圾回收器?
垃圾收集(GC),在Java中是一个非常重要的特性,在C++中,是没有GC机制的,分配了内存就需要释放,而对于GC来说,我们分配了内存不需要手动进行释放,GC垃圾回收机制会自动进行回收。
GC分为很多种类型。
垃圾回收器的分类:
按线程数分,可以分为串行垃圾回收器和并行垃圾回收器。
串行垃圾回收器一次只使用一个线程进行垃圾回收;并行垃圾回收器一次将开启多个线程同时进行垃圾回收。
按工作模式:
分为并发垃圾回收器及独占垃圾回收器
按碎片处理:
压缩垃圾回收器及非压缩垃圾回收器
按代分:
新生代垃圾回收器及老年代垃圾回收器。
1:
新生代并行收集器:
-XX:
+UseParNewGC
2:
新生代和老年代都用的并行回收器
-XX:
+UseParallelOldGC
3:
新生代和老年代都用的串行回收器
-XX:
+UseSerialGC
4:
CMS回收器
-XX:
+UseConcMarkSweepGC
测试四种垃圾回收器的性能
程序:
多次测试结果:
相对来说,串行收集器在四种收集器里相对来说较好,但是在Java1.7中出现了G1收集器,这里测试串行和G1的性能对比:
程序:
结果
查看GC日志,可以看出GC回收比较频繁
G1收集器测试
选择G1收集器并设置停顿时间不超过10ms
java-Xmx512M-Xms512M-XX:
+UseG1GC-XX:
MaxGCPauseMillis=10-Xloggc:
gc.log-XX:
+PrintGCDetails Test10
结果
GC日志
性能调优
调优方法:
1:
新对象预留新生代
2:
大对象进入老年代
3:
稳定与震荡的堆大小
4:
吞吐量优化
5:
降低停顿
一:
新对象预留在新生代
由于fullGC的成本要远远高于MinorGC,因此尽可能将对象分配在新生代是明智的。
大部分情况下JVM会尝试在eden区分配对象,但由于空间紧张等,很可能不得不将部分年轻对象向老年代压缩,所以调优时,可以为应用程序分配一个合理的新生代空间,以最大限度避免新对象直接进入老年代。
测试:
二:
大对象进入老年代
将大对象直接分配到老年代,保持新生代对象的结构的完整性,以提高GC效率。
-XX:
+PrintGCDetails-Xmx20M-Xms20M与-XX:
+PrintGCDetails-Xmx20M-Xms20M-XX:
PretenureSizeThreshold=1000000进行比较观察出大对象分配给老年代
-XX:
PretenureSizeThreshold:
设置大对象直接进入老年代的阈值,当对象的大小超过这个值,将直接在老年代分配
默认大对象位于新生代中,需要将其赶到老年代中,这时候需要设置新生代的年龄
-XX:
PretenureSizeThreshold= 即超过年龄进入老年代中去
三.稳定和震荡的堆大小
稳定的堆大小是对垃圾回收有利的,获得一个稳定的堆大小的方法是使-Xms和-Xmx的大小一致。
四.吞吐量优先
吞吐量优先的方案会尽可能减少系统的执行垃圾回收的总时间,故可以考虑关注系统吞吐量的并行回收收集器。
对于4G内存32核CPU的计算机上
使用以下参数设置:
1:
-Xmx3800M-Xms382800M
2:
-Xss128k:
减少线程栈大小
3:
-Xmn2G:
设置新生代大小
4:
-XX:
+UseParallelGC:
新生代并行回收器,这是一个关注吞吐量的收集器,可以尽量减少GC的时间
5:
-XX:
+ParallelGCThreads=20:
设置用于垃圾回收的线程数,通常情况下可以和CPU数量相等。
但CPU较多的情况下,设置相对较小的数值也是合理的
6:
-XX:
+UseParallelOldGC:
老年代也使用并行回收收集器。
五:
降低停顿
为了降低应用软件在垃圾回收的停顿,首先考虑的是使用关注停顿的CMS回收器,其次,为了减少FullGC次数,应尽量将对象预留在新生代,因为新生代的MinorGC成本远低于老年代的FullGC
可以如下设置降低停顿
1:
-XX:
ParalleGCThreads:
设置20个线程进行垃圾回收
2:
-XX:
UseParNewGC:
新生代使用并行回收器
3:
-XX:
+UseConcMarkSweepGC:
使用CMS收集器降低停顿
4:
-XX:
SurvivorRatio=8设置eden区和survivor区的比例为8:
1
5:
-XX:
TargetSurvivorRatio=90:
设置survivor使用率为90%
6:
-XX:
MaxTenuringThreshold=31:
年轻对象到老年对象的年龄默认是15次,也就是15次minorGC依然存活,股设置为30,即尽量让对象保存在新生代
六:
快照堆
在性能问题排查中,分析快照(Dump)是不可缺少的环节。
测试:
生成快照堆
载入查看
获取GC信息:
-verbose:
gc或-XX:
+PrintGC都能获取GC信息
-XX:
+PrintGCDetails:
获取更加详细的信息。
-XX:
+PrintGCTimeStamps:
获取GC的频率和间隔
-XX:
+PrintHeapAtGC:
获取堆的使用情况
-Xloggc:
D:
\gc.log:
指定日志情况
Tomcat调优实践
测试工具使用Jmeter进行调优测试吞吐量
代码:
package cn;
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.User;
@WebServlet("/TestLogin")
public class TestLogin extends HttpServlet {
private static final long serialVersionUID = 1L;
private Map
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
User user = new User();
user.setId
(1);
user.setName("renlu");
user.setPwd("123");
userdata.put(user,new byte[1024]);
System.out.println("Hello bf ");
request.getRequestDispatcher("success.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
实体
package com;
public class User {
private int id;
private String name;
private String pwd;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
测试环境apache-tomcat-7.0.67,javaversion"1.8.0_60",apache-jmeter-2.13,每次吞吐量都测试3次,取中间的值
测试线程数10,线程组200
默认未调优前的吞吐量
吞吐量:
317
调优一:
吞吐量508
调优二:
新生代并行回收器,这是一个关注吞吐量的收集器,可以尽量减少GC的时间
老年代也使用并行回收收集器。
吞吐量681
调优测试三:
使用-XX:
+UseConcMarkSweepGC
总结:
JVM调优的过程有:
确定堆内存大小(-Xmx-Xms)、合理分配新生代和老年代(-XX:
NewRatio、-Xmn、-XX:
SurvivorRatio),确定永久区的大小(-XX:
Permsize、-XX:
MaxPermSize)、选择垃圾收集器、对垃圾收集器进行合理的设置.当然还有禁用显示GC、禁用类元数据回收、禁用类验证等。
可以根据自己的实际情况进行调优。
网络摘抄:
JVM参数调优的八条经验
本文
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JVM 学习 笔记 大量 例子 保证 学会