java编译运行过程:
编译期:.java源文件,经编译,生成.class字节码文件
运行期:JVM加载.class并运行.class
JDK、JRE、JVM
JVM:java虚拟机
- 加载.class并运行.class
JRE:java运行环境
- 除了包含JVM以外还包含运行java程序所必须的环境
JRE=JVM+java系统类库
JDK:java开发工具包
- 除了包含JRE以外还包含开发java程序所必须的命令工具
JDK=JRE+编译、运行等工具
堆栈方法区
堆:
概述:
-
存储所有new出来的对象(包括实例变量)
-
垃圾回收器(GC)不定时到内存的堆中清扫垃圾
回收的过程是透明的,不一定一发现垃圾就立刻回收
调用System.gc()可以建议虚拟机尽快调度GC来回收 -
内存泄漏:不再使用的内存没有被及时的回收
建议:对象不再使用时,及时将引用设置为null -
内存溢出
-
实例变量的生命周期:
创建对象时存在堆中,对象被回收时一并被回收
栈:
- 存储正在调用中的方法中的所有局部变量(包括参数)
- 调用方法时在栈中为该方法分配一块对应的栈帧,
栈帧中包含方法的局部变量以及参数,
方法调用结束时,栈帧被清除,局部变量一并被清除 - 局部变量的生命周期:
调用方法时存在栈中,栈帧被清除时一并被清除
方法区:
- 存储.class字节码文件(包括方法、静态变量)
- 方法只有一份,通过this来区分具体的对象
JVM如何加载.class文件
- 类的加载是由类加载器(ClassLoader)及其子类完成,它主要负责在运行时查询和装入类文件中的类:
- 由于java的跨平台性,经过编译的java源程序不是一个可执行的程序,而是一个或多个类文件。类的加载是指把类的.calss文件中的数据读取到内存中,它会创建一个字节数组来读入.class文件,然后会产生与加载的类所对应的class对象,加载完毕后,这个class对象还是不完整的,这个类还是不可用的;接下来这个类会进入一个连接阶段,这个阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)、解析(将符号引用替换成直接引用)三个步骤;最后JVM对类进行初始化,包括:如果类存在直接的父类并且这个父类没有被初始化,那么就首先初始化父类;如果类中存在初始化语句,就会一次执行这些初始化语句。(读取到内存 - 连接阶段 - 初始化)
- 这些过程都是通过类加载器完成的,包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)、自定义加载器(java.lang.ClassLoader的子类)。从jdk1.2以后,类加载得过程采用了父类委托机制(PDM),指的是在这个机制中,JVM自带的Bootstrap是根加载器,其他的加载器有且仅有一个父类加载器。类的加载首先请求父类加载器,父类加载器无能为力时候才由其子类加载器加载,
- Bootstrap:负责加载JVM的核心类库
- Extension:从系统指定的目录加载类库,父加载器是Bootstrap
- System:应用加载器,父类是Extension,它会从环境变量classpath或者系统属性所指定的目录中加载类,是用户自定义加载器的默认父级加载器
JVM 调优
首先可以去了解一下新生代、老年代、永久代:
垃圾回收机制
-
针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;
- -Xms2g:初始化推大小为 2g;
- -Xmx2g:堆最大内存为 2g;
- -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
- -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
- –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
- -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
- -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
- -XX:+PrintGC:开启打印 gc 信息;
- -XX:+PrintGCDetails:打印 gc 详细信息。
-
年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代。
比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。
-
年轻代和年老代设置多大才算合理
-
更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
-
更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。
在抉择时应该根 据以下两点:
-
本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。
-
通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1但应该给年老代至少预留1/3的增长空间。
-
-
在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC 。
-
线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。
理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。