性能优化之JVM基础 性能优化JVM
性能优化 | JVM基础
前言
随着我们业务越来越复杂,高并发,高性能,都是一些系统在设计之初必须考虑的问题,这就需要对我们的程序进行性能的优化,而这涉及到的方面很多,这里主要从以下两个方面进行讲解:
- JVM的优化;
- TOMCAT的优化;
1、JVM基本组成部分
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。
JVM的基本组成结构包括:
- 类加载子系统
- 方法区
- Java堆
- 直接内存
- Java 栈
- 本地方法栈
- 垃圾回收系统
- PC寄存器
- 执行引擎
下面分别对这个组成部分,做简单的描述:
1)类加载子系统
负责从文件系统或者网络中加载Class信息,加载的信心存放在一块称之为方法区的内存空间。
2)方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
3)Java堆
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
4)直接内存
在JDK1.4中新加入了NIO(有的称为New I/O,有的称为Non-blocking I/O)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方法,它可以使用Native函数库直接分配队外内存,然后通过一个存储在Java对中的DirectByteBuffer对象作为这块内存的引用进行操作。
5)Java栈
每个虚拟机线程都有一个私有的栈,一个线程的JAVA栈在线程创建的时候被创建,JAVA栈中保存着局部变量、方法参数,同时还包括Java的方法调用,返回值等。
6)本地方法栈
本地方法栈和Java栈非常类似,最大不同为本地方法栈用于本地方法调用。Java虚拟机允许Java直接调用本地方法(通常使用C编写)。
7)垃圾收集系统是Java的核心,也是必不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理。
8)PC寄存器
PC寄存器也是每个线程私有的空间,Java虚拟机会为每个线程创建PC寄存器,在任意时刻,一个Java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,PC寄存器就会执行当前正在被执行的指令,如果是本地方法,则PC寄存器的值为undefined,寄存器存放如当前执行环节指针、程序计数器、操作栈指针、计算的变量指针等信息。
9)执行引擎
虚拟机最核心的组件就是执行引擎了,它负责执行虚拟机的字节码。一般会先编译成机器码后执行。
2、JVM各组成部分的联系
JVM各组成部分之间不是相互独立的,它们之间是有着紧密的联系的。
2.1、JAVA堆、JAVA栈以及方法区之间的联系
- 堆解决的是数据存储的问题,即数据怎么放,放在哪儿。
- 栈解决的是程序的运行问题,即程序如何执行,或者说如何处理数据。
- 方法区则是辅助堆栈的块永久区(Perm),解决堆栈信息的产生,是先决条件。
为了说清楚它们之间的关系,这里我们举一个例子说明,见如下代码:
public class User { private String id; private String name; private String address; public User() { } public User(String id, String name) { this.id = id; this.name = name; } public static void main(String[] args) { User user = new User(); user.setId("1"); user.setName("zhangsan"); System.out.println(user.getId()); } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public String getId() { return id; } public String getName() { return name; } }
这里定义了一个User类,那么这个类是如何存储和运行的呢?我们从以下几个阶段来描述:
1)User类及其方法实现是存储在方法区中的,包括User类信息、常量、静态变量等数据。
2)当我们执行User user = new User();的时候,User类的实例user是存储在堆中的,如果这个时候我们定义另外一个实例User user2 = new User();,那么user2这个实例也是存储在Java堆中的。
3)对于实例user以及user2,它们的局部变量都是存储在Java栈中的。
为了更直观的表达它们之间的关系,见如下图所示:
这些都是运行在JVM中的,那么如果来看JVM中这些内存的变化的呢?有没有一个工具可以看到呢?答案是肯定的,这里提供两款工具供大家参考:
- JDK根目录下的bin目录下有个jconsole.exe;
- Java Visual VM;
这里主要介绍jconsole.exe,如下:
1)打开jconsole.exe之后,会弹出建立新连接的提示框,如图:
2)连接之后,会出现如图所示内容:
可以发现这里可以观察到的参数指标有以下几类:内存、线程、类、VM概要、MBean,这里主要讲解内存这块的内容,其他的指标大家可以自行查看。
3)这里内存监控指标包括:
堆内存使用量;
非堆内存使用量;
内存池(PS Old Gen);
内存池(PS Eden Space);
内存池(PS survivor Space);
等等。
如图所示:
这里我们手工进行GC。
另外还有一部分需要注意。也就是Java堆内存中的划分,
根据垃圾回收机制不同,JAVA堆又有不同的结构。最常见的就是将整个JAVA堆分为新生代和老年代。
何为新生代和老年代?
新生代和老年代的划分并不是根据该对象在内存中存在的时间来定义的,而是通过GC的过程中对该对象的回收次数决定的。每GC一次,那么该对象的回收次数增加1,当该值达到定义的阈值时,则该对象进入老年代。
上一篇: 高效sql性能优化(复习)