JVM虚拟机的基本结构:jvm通过类加载器将.class文件加载到jvm的内存空间运行,GC垃圾回收器对jvm的一个内存空间监控并进行对应的垃圾回收。
JVM的内存分配:由上图可以看出jvm的内存分配主要分为方法区、堆、本地方法栈、java线程栈、PC寄存器这5个区
方法区:用于存放类结构的信息、包括常量池、静态变量、构造函数等类型信息、这些信息是由类加载器在类加载的时候从.class文件中读取到方法区中。方法区同样存在垃圾回收、因为用户在通过自定义加载器加载的一些类同样会成为垃圾、jvm会回收一个未被引用类所占的内存。这个区域的数据是线程共享的。
堆:堆是JVM的一个很重要内存空间,它用来存储和管理我们java实例或者对象(数组、类的实例化对象等)的地方,也是垃圾回收的主要区域,这个内存区域的数据是内存共享的,也就是说每个用户实例化的对象都存储在堆中,并且数据都从这个共享堆中获取。
java栈:也可以叫作线程栈,这个区域和我们线程息息相关,每创建一个线程,jvm都会为我们创建一个线程栈存放这个线程的信息,包括:局部变量、程序运行状态、方法返回值(java参数传递是一个值传递)、基本数据类型的,对象的引用地址等。java栈是一个线程私有的内存空间,里面存储的数据只有当前线程可见,其他线程不可访问。这个栈中包含了很多个栈贞,每调用一个方法就会创建一个栈桢保存方法的信息数据等进行压入java栈中--压栈,一个方法从调用到返回的这个过程称之为一个入栈到出栈的一个过程。虚拟机栈是一个后入先出的数据结构,线程运行过程中,只有一个栈帧是处于活跃状态的,被称为"当前活动帧栈",当前活动帧栈始终是虚拟机栈的栈顶元素
本地方法栈:本地方法栈和java栈的作用相似,不过本地方法栈是为Native方法服务的,对于一个运行中的Java程序而言,它还可能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区
PC寄存器:pc寄存器是jvm中一块较小的内存区域,里面存储着当前线程的虚拟机字节码指令的内存地址(类似于汇编语言中的指令地址),java的多线程机制的实现,就是通过轮流切换并且分配处理器执行时间的方式来实现,在任何时间,处理器都只会执行一个线程中的指令、在多线程先、为了保证线程切换前后、保证前后的状态一直、找到之前的执行指令地址就需要这个PC寄存器来进行存储,因为这个内存区域也是线程私有的。
JVM类加载机制:我们的.java源码文件通过编译之后会生成一个JVM可识别的二进制字节码.class文件,每一个类都会产生一个Class对象,保存在对应的.class字节码文件中,类装载器在会将这些类装载到JVM中。类装载器一般会在类的首次使用时或者一个静态(static)成员引用时,就会触发类加载器动态的将类加载到JVM中(另外JVM预先加载某些类也是被允许的),加载过程中,类的加载器会先在JVM的静态方法区检查一遍这个Class对象时候已经被加载过了,如果没有加载,则默认的加载器就会根据类名查找对应的.class文件,将其读取到jvm中,然后将这个类的信息保存在静态方法区,类的信息包括:类的全限定名、父类的全限定名、字段信息、方法信息、类的访问修饰符等。这是整个类的加载机制;
了解了JVM的内存空间以及类的加载和运行机制,我们可以针对自己的项目的并发数,ops等性能要求配置怼、栈的内存以及一些回收算法的参数配置;我们可以在jvm启动的时候设置,omcat默认的Java虚拟机JVM启动内存参数大约只有64MB或者128MB,非常小,远远没有利用现在服务器的强大内存,所以要设置Java虚拟机JVM启动内存参数。具体设置方法为:Tomcat修TOMCAT_HOME/bin/catalina.bat,在[echo Using CATALINA_BASE: "%CATALINA_BASE%"] 上面加入,比如:set JAVA_OPTS= -server -Xms1536m -Xmx1536m或者JAVA_OPTS="-server -Xms1536m -Xmx1536m",服务器模式参数-server不加也可以 ,就变成set JAVA_OPTS= -Xms1536m Xmx1536m或者JAVA_OPTS=" -Xms1536m -Xmx1536m";但是JVM启动内存参数也不可能无限增大,因为他受限于物理内存和操作系统的限制,Windows操作系统下一般可以使用1.6GB左右的Java虚拟机内存,有些JDK如Jrockit可能最大可达1.9GB左右,此外,Linux、小型机AIX等情况可以较大。
-Xmx3550m | 设置JVM最大堆内存为3550M |
-Xms3550m | 设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存 |
-Xss128k | 设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能 |
-Xmn2g | 设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8 |
-XX:NewSize=1024m | 设置年轻代初始值为1024M |
-XX:MaxNewSize=1024m | 设置年轻代最大值为1024M |
-XX:PermSize=256m | 设置持久代初始值为256M |
-XX:MaxPermSize=256m | 设置持久代最大值为256M |
-XX:NewRatio=4 | 设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。 |
-XX:MaxTenuringThreshold=7 | 表示一个对象如果在Survivor区(救助空间)移动了7次还没有被垃圾回收就进入年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。 |