文章目录
Java虚拟机
什么是java虚拟机
所谓虚拟机,就是一台虚拟的机器,它是一款软件,用来执行一系列虚拟计算指令,他们完全是对物理计算的仿真,提供了一个可以运行完整操作系统的平台。
java虚拟机,它专门为执行单个计算程序而计算,在java虚拟机中执行的指令我们称为java字节码指令。
在虚拟机上运行的软件都限制于虚拟机提供的资源中。
Java内存结构
什么是java内存结构
JVM虚拟机存储空间。
java文件编译后变为class文件,classLoader类加载器读取class文件,分配资源到哪个内存空间去。
内存空间:
-
方法区
方法区又称为永久区,static关键字修饰的存放在方法区,方法区一般存放常量信息,当class文件被加载的时候,方法区就会被初始化,方法区是全局的。 -
堆
创建(new)的对象,数组等等,存放在堆内存中。堆内存所有线程都会共享, -
栈
存放定义基本局部变量,好处是代码运行完毕,自动释放内存。每个线程私有,互不共享,栈不会产生线程安全问题。类的方法存放在栈中。 -
本地方法栈
主要作用是调用C语言,安卓开发中,底层可能会用C语言,java去使用C语言用到JNI技术,会用到本地方法栈。
PC寄存器:
每个线程都有PC寄存器,每个线程的PC寄存器是私有的。执行命令的指针
执行引擎:
负责执行字节码文件
调优策略主要是堆和垃圾回收机制。
堆
堆内存
new出来的对象都会存放在堆内存中,堆内存中分配两个区,分为新生代和老年代,分代作用是利于垃圾回收机制
新生代,垃圾回收机制不经常回收的区域。
老年代:如果对象被频繁的使用,将对象放入到老年代中。
新生代里又分为eden、s0、s1区。其中s0和s1区大小相等,目的是垃圾回收复制算法。其中s0、s1又称为from/to区
- eden:刚创建的对象存放在eden区
- s0、s1:在eden区的对象经常被使用后转移到s0或者s1区。
- 老年代:如果s0或者s1区的对象仍在频繁使用就晋升到老年代区,到了老年代的对象不会回到新生代,是不可逆的转移过程。
垃圾回收机制主要回收新生代。
调优:尽量减少垃圾回收机制的次数。在web系统中尽量减少常量信息。尽量减少老年代的回收次数,提高新生代的回收次数。
堆内存参数配置
什么是虚拟机参数配置?
使用给定的参数执行java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。
主要围绕堆、栈、方法区进行配置
堆的配置参数
参数 | 说明 |
---|---|
-XX:+PrintGC | 每次触发GC时打印相关日志 |
-XX:+UserSerialGC | 串行回收 |
-XX:+PrintGCDetails | 更详细的GC日志 |
-Xms | 堆初始值 |
-Xmx | 堆最大可用值 |
-Xmn | 新生代堆最大可用值 |
-XX:SurvivorRatio | 用来设置新生代中eden空间和trom/to空间的比例 |
实际工作中,我们一定要把初始的堆大小与最大堆大小相等,这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率
显示堆参数例子
import java.text.DecimalFormat;
public class Test1 {
public static void main(String[] args) throws InterruptedException {
// 新建byte数组
byte[] bytes1 = new byte[1024 * 1024];
System.out.println("分配1M内存");
jvmInfo();
Thread.sleep(5000);
byte[] bytes2 = new byte[4 * 1024 * 1024];
System.out.println("分配4M内存");
jvmInfo();
}
/**
* 比特转换为M
*
* @param bt 多少比特
* @return {@link String} 多少M,保留两位小数
*/
static public String toM(long bt) {
float num = (float) bt / (1024 * 1024);
// 设置小数格式
DecimalFormat df = new DecimalFormat("0.00");
return df.format(num);
}
/**
* 显示jvm信息
*/
static public void jvmInfo() {
// 最大内存配置信息 单位K
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("最大内存:" + toM(maxMemory) + " M");
// 当前空闲内存
long freeMemory = Runtime.getRuntime().freeMemory();
System.out.println("当前空闲内存:" + toM(freeMemory) + " M");
// 返回Java虚拟机中的内存总量
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("内存总量:" + toM(totalMemory) + " M");
// 返回Java虚拟机可用的处理器数量
long availableProcessors = Runtime.getRuntime().availableProcessors();
System.out.println("可用的处理器数量:" + availableProcessors);
}
}
totalMemory()应该是已用内存的意思
配置堆参数-初始值和最大值
Edit Configurations
VM arguments添加参数
-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
运行结果
这一条日志表示GC已经回收了
[GC (Allocation Failure) [DefNew: 1664K->192K(1856K), 0.0015332 secs] 1664K->626K(5952K), 0.0211005 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
可以看到GC回收了两次
我们把参数 -Xms5m -Xmx20m改成 -Xms20m -Xmx20m,初始值和最大值相等,看看结果,就只回收了一次
为什么初始值越小,垃圾回收机制次数越多?
初始值越小,当要分配较大的内存时,空间不够,分配内存的时候就越需要进行回收
配置堆参数-设置新生代参数
-Xmn 新生代大小
-XX:SurvivorRatio 设置新生代中eden区与from/to空间的比例
VM arguments添加参数
-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
设置新生代大小为2m,eden与from/to比例为2:1
public class Test2 {
public static void main(String[] args) {
byte[] bytes = null;
for (int i = 0; i < 10; i++) {
System.out.println(i);
bytes = new byte[1024*1024];
}
}
}
运行结果,eden与from/to比例为2:1
配置堆参数-设置新生代与老年代比例
-XX:NewRation 设置老年代/新生代的大小
基本策略:尽可能将对象留在新生代,减少老年代的GC次数。
VM arguments添加参数
-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2
-XX:NewRatio=2,则Old Generation是 Yong Generation的2倍,即Yong Generation占据内存的1/3,垃圾回收机制主要回收新生代,尽量减少垃圾回收机制的次数,一般Yong Generation设成占据内存的1/3或者1/4
堆溢出解决办法
堆溢出问题模拟
VM arguments设置参数
-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError 打印问题溢出问题
创建10M的List
public static void main(String[] args) {
List<Object> list = new ArrayList<Object>();
for (int i = 0; i < 10; i++) {
list.add(new byte[1024 * 1024]);
}
System.out.println("创建完毕");
}
加堆内存大小即可, -Xms30m -Xmx30m
Tomcat内存溢出在catalina.sh修改堆内存大小
JAVA_OPTS:(可选)在执行任何命令时使用的Java运行时选项。JAVA_OPTS中的所有选项应该被Tomcat、停止进程、版本命令等使用。
JAVA_OPTS="-server -Xms800m -Xmx800m -XXNewSize=256M -XX:PermSize=256M -XX:MaxNewSize=512m -XX:MaxPermSize=512m"
栈
什么是栈溢出
操作变量时无限的递归进行调用会造成栈溢出,循环调用方法不会造成栈溢出
栈溢出解决方法
栈溢出问题模拟
public class Test4 {
static int count = 0;
public static void getCount() {
try {
count++;
getCount();
} catch (Throwable e) {
System.out.println("深度:" + count);
e.printStackTrace();
}
}
public static void main(String[] args) {
getCount();
}
}
配置参数只能增加深度,而不能解决无限递归造成的栈溢出问题
-Xss5m 设置最大调用深度
从1万2千多到26万多