1、概述
jvm,java虚拟机,模拟计算机的各项功能,将编译后的java字节码解释成具体平台的机器指令进行运行,从而达到“一次编译,到处运行”;
流程:java编译器将java代码编译成字节码class文件,类加载器依靠java类库对字节码进行 加载、验证、准备、解析、初始化、使用、卸载等操作;
加载:将创建一个字节数组,读取字节码文件;
验证:格式、字节码、元数据、符号饮用的验证;
准备:为静态变量分配空间并设置默认值;
解析:对 对象对符号饮用替换为指针直接引用;
初始化:初始化父类或者执行初始化语句;
加载器:启动类加载器/扩展类加载器/应用程序类加载器;--将字节码文件加载到内存中,转化成对象;
双亲委派模型:收到类加载请求,加载器会先请求父类加载器去尝试加载,父类搜索不到,子类才加载;---防止不同加载器加载多次,出现多份同样的字节码文件;
打破双亲委派:继承ClassLoader类,重写loadClass和findClass;
2、jvm模型结构
方法区和堆是所有java线程共享的;方法区:类结构信息,静态变量、构造函数、常量池; 堆:最主要的工作内存区域,存放几乎所有的java对象;
java栈/jvm栈:是线程的私有的工作内存空间,每当创建线程时,就会为其分配一个工作内存空间;存放基本数据类型、对象的引用;java方法;
本地方法栈:java方法有时会调用本地方法、native方法;线程的setPriority是java写的,但是实现调用的是C语言实现的,在本地方法栈中;
程序计数器:线程私有,用于记录线程执行的指令地址,便于上下文切换时恢复到原有状态位置;
jvm栈,
有后进先出的栈帧队列,每调用一个函数都会对应入栈,执行完毕出栈,当栈桢队列深度超出jvm允许最大深度时抛StackOverflowError,一般是 递归中会发生;
jvm栈是可以动态扩展的,当内存不足时,新线程无法分配到内存空间,则抛出OutOfMemory,一般是 创建过多线程会发生;
3、新生代与老年代
堆中存放着几乎所有的对象,通过回收机制,自动清理垃圾对象,实现自动管理;
新生代分为:eden、survivor0、survivor1:
GC:新生代的eden空间存满时触发Young GC(Minor GC--对象年龄+1;清理新生代的对象到survivor,survivor占用率会升高,eden+s0>s1则放到老年代,老年代剩余空间不足则直接Full GC);老年代空间存满时触发Full GC(Major GC--清理整个堆);经过一定次数(15)GC存活下来的新生代对象升级到老年代,大对象直接进入老年代;
增加survivor可以减少送到老年代对象的数量,进而减少Full GC的发生;Full GC通常会至少会有1次Young GC,且时间是Young GC的十倍以上;
两个survivor:首次GC,存活对象移到s0,清空eden;二次GC时,将eden和s0的存活对象移到s1;清空eden和s0;这样,保证了存活对象占用连续的内存空间,避免了碎片化;
–XX:NewRatio ----新生代与老年代比例,默认是1:2;
–XX:SurvivorRatio ----新生代各个区域比例,默认是Edem : from-s0 : to-s1 = 8 : 1 : 1
-XX:+MaxTenuringThreshold ---对象被GC/复制次数,默认15
永久代的变化:
JDK1.8前:类的元数据如方法数据、方法信息(字节码,栈和变量大小)、运行时常量池、已确定的符号引用和虚方法表等被保存在永久代中;32位为64M,64位为85M,超出会抛OOM;
JDK1.8及以后:永久代从Java堆中移除,类的元数据直接保存在本地内存区域,称之为元空间;(永久代大小难确定,调优困难,有效避免内存溢出)
GC发生时确定堆中对象是存活,两种方法:引用计数法、可达性分析法。
引用计数法:对象上有引用计数器,被引用时计数器+1,使用完毕计数器-1,0时表示不可能再被使用,实现简单,判断高效,但不能解决相互引用问题;
可达性分析法:以GCRoots对象做起点,搜索引用链上的对象,当一个对象到 GC Roots没有任何引用链时,意味着该对象可以被回收。
4、GC收集器与收集算法
收集器:
Serial收集器:单线程,复制算法;
ParNew收集器:多线程,复制算法;
Parallel收集器:年轻代、高吞吐量、多线程,复制算法;
Parallel Old 收集器:老年代、多线程,标记整理算法;
CMS收集器:最短回收停顿时间,标记清除算法;--初始标记/并发标记/重新标记,并发清除,大量碎片;
G1收集器:标记整理算法;--初始标记,并发标记,最终标记,筛选标记,无碎片;
CMS收集器和G1收集器的区别:
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;以最小的停顿时间为目标;标记清除算法;易产生碎片;
G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;可预测停顿时间;标记整理算法;进行空间整合,降低了内存空间碎片。
收集算法:
复制算法--常用于新生代,复制对象较少,因为存活对象少:
将内存空间分为两个相等区域,每次只使用一个;当垃圾回收时,将当前区域正在使用对象复制到另一个区域;复制成本小,且空间被整理,无碎片;但需要两倍空间;
标记整理算法--常用于老年代,老年代存活对象较多,不适合用复制:
标记+整理,标记要回收的对象,整理移动存活对象到一端,然后清理边界外对象数据; 无碎片,整理耗时; 最耗时、最慢,需要移动对象;
标记清除算法--常用于老年代:
标记+清除,标记要回收的对象,统一回收所有被标记的对象; 有碎片,标记耗时; 中等速度,无需移动对象;
5、引用类型
强引用:GC时不会被回收,即便是内存不足抛出异常;
弱引用:时间较短,发现时就会被回收;
软引用:内存不足时回收;
虚引用:任何时候都可以被回收,无用;唯一目的就是回收时收到通知;监控对内存地址的引用;
强引用 > 软引用 > 弱引用 > 虚引用
持久代:存放类名、方法、常量池信息、对象数组、jvm内部对象等;默认容量是32/64M;JDK8后元空间取代直接放到内存中了,不占虚拟机空间;
6、参数配置
调优与监控:top -Hp pid:cpu的耗时与性能;
jstat:编译、类加载内存、垃圾收集等运行时的状态信息;jstat -class <pid>
jstack:某时刻的线程快照,查看堆栈信息;jstack -l <pid>
jdk-bin路径下:jvisualvm/jconsole配置ip和端口;tomcat加监控端口;-- 堆大小/使用/cpu使用/GC活动/新生代老年代/线程数量/vm参数,页面类似win任务管理器
堆栈:
-Xmx3550m--最佳1/4内存 //最大堆大小为3550m -Xms3550m --1/64 设置一致,避免堆内存申请伸缩扩容,且满了才触发GC,初期GC减少 //设置初始堆大小为3550m -Xmn2g --3/8 //设置年轻代大小为2g -Xss128k 256kb-1M //每个线程的堆栈大小为128k -XX:MaxPermSize=16m --1/64 //设置持久代大小为16m -XX:NewRatio=4 1.3/1.4 //设置年轻代与年老代的比值 -XX:SurvivorRatio=4 8.1.1 //设置年轻代中Eden区与Survivor区的大小比值 -XX:MaxTenuringThreshold=0 -串行GC有效 //设置垃圾最大年龄,进入年老代
垃圾收集器:
-XX:+UseParallelGC //选择垃圾收集器为并行收集器 -XX:ParallelGCThreads=20 //配置并行收集器的线程数 -XX:+UseConcMarkSweepGC //设置年老代为并发收集 -XX:CMSFullGCsBeforeCompaction=5 //设置运行多少次GC以后对内存空间进行压缩、整理 -XX:+UseCMSCompactAtFullCollection //打开对年老代的压缩
辅助信息:
-XX:+PrintGC //输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs][Full GC 121376K->10414K(130112K), 0.0650971 secs] -XX:+PrintGCDetails //输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs
内存堆不足java.lang.OutOfMemoryError: Java heap space -- 增加xmx/xms
内存栈不足java.lang.OutOfMemoryError: unable to create new native thread -- 减小Xss
内存持久不足java.lang.OutOfMemoryError: PermGen space-- 增加MaxPermSize
内存溢出java.lang.StackOverflowError -- 业务逻辑,梳理减少递归次数,增加Xss大小
堆小回收时间长java.lang.OutOfMemoryError: GC overhead limit exceeded -- 用来预测抛异常