公司动态
JVM从入门到精通
初识JVMjvm就是java虚拟机是一个运行在计算机上的文件职责是运行java字节码文件jvm的功能jvm的组成主要有四个部分类加载器(ClassLoader)运行时数据区执行引擎本地方法栈字节码文件的组成1基础信息魔数编译时使用的jdk版本号访问标识(private,public...),父类和接口2常量池字符串常量类或接口名字段名3字段当前的类或接口声明的字段信息4方法当前类或方法声明的接口信息5属性类的属性比如码源的文件名或内部类列表等字节码文件常见工具使用1.javap -v是jdk自带的反编译工具他的展示效果一般但是操作方式相对简单可以查看服务器里的字节码文件2.idea的jclasslib插件3.Arthas是阿里开源的向上诊断工具可不重启服务排查CPU 高、死锁、慢接口、内存泄漏、查看方法入参 / 返回值、热更新代码等。相关的命令类的生命周期加载阶段具体步骤1.类加载器根据类的全限定名通过各种渠道以二进制流的方式获取字节码信息2.类加载完成之后jvm会将信息保存到方法区中3.类加载完成之后jvm会将信息保存到内存的方法区中4.同时jvm还会在堆中生成一份与方法区类似的java.lang.Class对象连接阶段初始化阶段1.会执行静态代码块中的代码并为静态变量赋值2.会执行字节码文件中的clinit部分字节码指令需要注意的是使用阶段卸载阶段类加载器ClassLoader是jvm提供给应用程序去实现获取类和接口获取字节码数据的技术应用场景类加载器的分类主要分为两类java实现的jvm底层源码实现的启动类加载器是由hotspot虚拟机提供的使用C编译的类加载器默认加载java安装目录/jre/lib下的类文件扩展类加载器应用程序类加载器双亲委派机制1.保证类加载的安全性可以避免恶意代码替换jdk中的核心类库2.避免重复加载可以避免同一个类被重复加载如何使用代码的方式去主动加载一个类1.使用Class.forName()方法2.获取类加载器使用类加载器的loadClass方法指定某个类加载器加载打破双亲委派机制使用场景1.自定义类加载器loadClass包含了四个核心方法自定义类加载器需要继承ClassLoader然后重写findClass方法这样不会破坏双亲委派机制如果重写loadClass就会打破双亲委派机制2.线程上下文类加载器3.Osgi框架的类加载器JDK9之后的类加载器jdk8及之前的版本中扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sum.misc.Launcher.javajdk9引入了module的概念类加载器设计上发生了很多变化1.启动类加载器由java编写位于jdk.internal.loader.ClassLoader类中2.扩展类加载器被替换成了平台类加载器运行时数据区主要分为线程共享和线程不共享两部分主要的应用场景程序计数器也叫PC寄存器每个线程都会通过程序计数器记录当前要执行的字节码指令的地址不会出现内存溢出的问题并且是线程私有的栈java虚拟机栈采用了栈的数据结构先进后出每一个调用使用一个栈帧来保存栈帧的组成栈帧中的局部变量表是一个数组数组中的每一个位置称之为槽long,double占两个槽其他类型占一个槽操作数栈用来存放临时数据或中间数据的java虚拟机栈会出现内存溢出StackOverflowError错误栈的默认大小可以使用-Xss栈大小 指定栈的大小本地方法栈为native修饰的方法提供的栈帧堆是空间最大的一块内存区域创建出来的对象都存与堆上栈上面的局部变量表可以存储堆上的对象的引用。静态变量也可以存放堆对象的引用通过静态变量就可以实现对象在线程之间共享。如果创建的对象太多了超出了堆内存的空间就会抛出OutOfMemoryError对空间有三个需要关注的值used total max可以通过dashboard命令看到可以使用-Xms值 (total)-Xmx值 (max)设置最大堆内存的值方法区是一块线程共享的区域存储类的元信息(在类的加载阶段加载进来)运行时常量池字符串常量池方法区中的字符串常量池方法区中除了类的元信息运行时常量池之外还有一块区域叫字符串常量池(StringTable)用来存储再代码中定义的常量字符串内容。方法去的发展过程例题internString.intern()可以手动将字符串放入字符串常量池中分别在JDK6 JDK8下执行代码 JDK6中的结果是false falseJDK8中是true false注意JVM启动时就会把java加入到常量池中jdk6版本中的intern()方法会把第一次遇到的字符串实例复制到永久代的字符串常量池中返回的也是永久代里面这个字符串实例的引用。JVM启动时就会把java加载到常量池中jdk7及之后版本由于字符串常量池在堆上所以intern()方法会把第一次遇到的字符串的引用放入字符串常量池。静态变量的存储jdk6存储在方法区中jdk7及之后的版本存放在堆中的class对象中脱离了永久代直接内存在JDK1.4中引入了NIO机制使用了直接内存主要时是为了解决两个问题1.java堆中的对象如果不在使用要回收回收时会影响对象的创建和使用。2.IO操作比如读文件需要先把文件读入直接内存再把数据复制到java堆中自动垃圾回收机制内存泄露指的是不在使用的对象在系统中没有被回收内存泄漏的积累可能导致内存溢出Java为了简化对象的释放引入了自动的垃圾回收机制。主要负责堆上进行垃圾回收方法区的回收类的生命周期回顾判定一个方法区中的类可以被卸载需要同时满足以下三个条件1.此类所有实例都已被回收在堆中不存在任何该类的实例对象以及子类对象。2.加载该类的类加载器已经被回收。3.该类对应的java.lang.Class对象没有在任何地方被引用。堆回收引用计数法可达性分析法Java中的对象是否能被回收是根据对象是否被引用来决定的。如果对象被引用了说明该对象还在使用不允许被回收如何判断堆上的对象又没有被引用常有两种判断方法引用计数法和可达性分析引用计数法每当一个对象被引用之后计数加一有两个缺点1.每次引用和取消引用都需要维护计数器对系统性能有一定的影响2.存在循环引用的问题a引用bb引用a计数器就会一致增长导致a,b无法回收出现了内存泄漏可达性分析算法将对象分为两类垃圾回收的根对象和普通对象对象与对象之间存在引用关系可达性分析算法就是如果某个到GC Root对象是可达的对象就不可被回收哪些对象可以被成为根对象1.线程Thread对象2.系统类加载器加载的java.lang.Class对象3.监视器对象用来保存同步锁synchronized关键字持有的对象4.本地方法调用时使用的全局对象arthas和eclipse Memory Analyzer(MAT)工具可以查看GC root,MAT工具时eclipse推出的Java堆内存检测工具;1.使用arthas工具的heapdump命令将堆内存快照保存到本地磁盘中。2.使用MAT工具打开堆内存快照文件MAT中的GC root3.选择GC root功能查看所有GC root五种对像引用1.强引用可达性算法中描述的对象引用一般指的是强引用即是GC Root对象对普通对象的引用关系只要这层关系存在普通对象就不会被回收。2.软引用如果一个对象只有软引用关联到它当程序内存不足时就会将软引用中的数据进行回收。JDK1.2版之后提供了SoftReference类来实现软引用软引用常用于缓存中。软引用的执行过程如下1. 将对象使用软引用包装起来new SoftReference 对象类型(对象)。2. 内存不足时虚拟机尝试进行垃圾回收。3. 如果垃圾回收仍不能解决内存不足的问题回收软引用中的对象。4. 如果依然内存不足抛出 OutOfMemory 异常。3.弱引用弱引用的整体机制和软引用基本一致区别在于弱引用包含对象在垃圾回收时不管内存够不够都会被直接回收。在JDK1.2之后提供了WeakReference类来实现弱引用弱引用主要在ThreadLocal中使用。弱引用对象本身也可以使用引用队列进行回收。4.虚引用虚引用也叫幽灵引用 / 幻影引用不能通过虚引用对象获取到包含的对象。虚引用唯一的用途是当对象被垃圾回收器回收时可以接收到对应的通知。Java 中使用 PhantomReference 实现了虚引用直接内存中为了及时知道直接内存对象不再使用从而回收内存使用了虚引用来实现。5.终结器引用终结器引用指的是在对象需要被回收时对象将会被放置在 Finalizer 类中的引用队列中并在稍后由一条由 FinalizerThread 线程从队列中获取对象然后执行对象的 finalize 方法。在这个过程中可以在 finalize 方法中再将自身对象使用强引用关联上但是不建议这样做如果耗时过长会影响其他对象的回收。垃圾回收算法垃圾回收要做的两件事1.使用可达性分析算法找到内存中存活的垃圾2.释放不存活对象的内存使程序能够再次使用这部分内存垃圾回收算法的历史和分类1.标记-清除算法核心思想分为两步标记阶段将所有存活的对象进行标记。Java中使用可达性分析算法从GC Root开始通过引用链遍历出所有存活的对象清除阶段从内存中删除所有没有被标记的对象缺点碎片化问题由于内存是连续的所以对象被删除之后内存中会出现很多细小的可用的内存单元。如果我们需要的是一个比较大的空间很有可能这些内存单元的大小无法进行分配分配速度慢由于内存碎片的存在需要维护一个空闲链表极有可能发生每次遍历到链表的最后才能获得合适的内存空间2.复制算法核心思想是一.准备两块空间From和To空间每次在对象分配阶段只能使用其中一块空间(From空间)二.在垃圾回收GC阶段将From中存货对象复制到To空间三.将两块空间的From和To名字互换优点吞吐量高复制算法只需要遍历一次存活的对象复制到To空间即可标记-整理算法少了一次遍历的过程因而性能比较好但不如标记-清除算法因为标记清除算法不需要进行对象的移动。不会发生碎片化。复制算法在复制之后就会将对象按顺序放入To空间中所以对象以外的区域都是可用空间不存在碎片化内存空间。缺点内存使用率低。每次只能让一半的内存空间来为创建对象使用3.标记-整理算法标记阶段使用可达性分析算法标记所有存活的对象整理阶段将存活的对象移动到堆的一端。清理掉存活对象的内存空间优点内存使用率高不会发生碎片化缺点整理阶段的效率不高4.分代GC会将整块内存划分为两块区域分为年轻代老年代首先创建出来的对象会被放入Eden随着Eden区越来越多如果Eden区满新创建的对象已经无法放入就会触发年轻代的GC称为Minor GC或Young GCMinor GC会把需要eden中和From需要回收的对象回收把没有回收的对象放入To区交换From和To的名称每次Minor GC中都会为对象记录他的年龄初始值为0每次GC完加1当对象的年龄达到15后对象就会晋升到老年代(这个晋升年龄和垃圾回收器有关)当老年代空间不足无法放入新的对象时先尝试minor gc如果还是不足就会触发Full GC,Full GC会对整个堆进行垃圾回收。垃圾回收过程会通过单独的GC线程来完成。但是不管使用哪一种GC算法都会有部分阶段需要停止所有的用户线程。这个过程被称之为Stop The Word简称为STW如果STW时间过长则会影响用户的使用。判断GC算法是否优秀可以从三个方面来考虑1.吞吐量吞吐量指的是CPU用于执行用户代码的时间与CPU总执行时间的比值。即吞吐量执行用户代码时间/(执行用户代码时间GC时间)。吞吐量数值越高垃圾回收的效率就越高。比如虚拟机总共运行了100分钟其中GC花掉了1分钟那么吞吐量就是99%2.最大暂停时间指的是在所有垃圾回收过程中的STW的最大值3.堆只用效率不同的垃圾回收算法对堆的内存使用方式是不同的。比如标记算法可以使用完整的堆内存而复制算法会将堆内存一分为二每次只能使用一半内存。从堆使用效率来说标记清除算法要优于复制算法。上述三种评价标准堆使用率最大暂停时间吞吐量不可兼得一般来说堆内存越大最大暂停时间越长想要减少最大暂停时间就会降低吞吐量所以不同的垃圾回收算法适用于不同的场景常见的垃圾回收器第一种组合Serial-SerialOldSerial年轻代Serial是一种单线程回收年轻代的垃圾回收器SerialOld老年代Serial是Serial垃圾回收器的老年代版本采用单线程串行回收第二种组合ParNew-CMSParNewParNew垃圾回收器本质上是对Serial在多CPU下的优化使用多线程进行垃圾回收CMS关注的是系统暂停的时间允许用户线程和垃圾回收线程在某些步骤中同时进行减少了用户线程的等待时间第三种组合Parallel Scavenge-Parallel Old年轻代Parallel Scavenge-是JDK8默认的年轻代垃圾回收器多线程并行回收关注的是系统的吞吐量。具备自动调整堆内存大小的特点老年代Parallel Old垃圾回收器是为Parallel Scavenge收集器设计的老年代版本利用多线程并发收集。G1垃圾回收器JDK9之后的默认垃圾回收器是G1垃圾回收器Parallel Scavenge关注吞吐量允许用户设置最大暂停时间但是会减少年轻代可用空间大小。CMS关注暂停时间但是吞吐量方面会下降。而G1设计目标就是将上述两种垃圾回收器的优点融合1.支持巨大的对空间回收并由较高的吞吐量。2.支持多CPU并行垃圾回收。3.允许用户设置最大暂停时间。G1垃圾回收器的内存图G1的整个堆会把划分为多个大小相等的区域称之为区Region区域不要求是连续的分为Eden,Survivor,Old区。Region的大小通过对空间/2048计算得到也可以通过参数指定Region size必须是2的指数幂取值范围从1M-32MG1的垃圾回收有两种1.年轻代回收回收Eden,Survivor中不用的对象。会导致STWG1中可以设置参数调整每次垃圾回收时的最大暂停时间毫秒数G1垃圾回收器会尽可能地保证暂停时间2.年代回收G1垃圾回收器执行流程1.新创建的对象会放在Eden区。当G1判断年轻代区不足(max默认60%)无法分配对象是需要回收时会执行Young GC2.标记出Eden和Survivor区域中的存活对象3.根据最大的暂停时间选择某些区域将存活的对象复制到一个新的Survivor中(年龄1),清空这些区域4.后续的Young GC与之前相同只不过Survivor区中存活的对象会被搬运到另一Survivor区5.当某个存货对象的年龄到达阈值(默认15)将被放入老年代6.如果有些对象大小超过了Region的一半会直接放入老年代这类老年代被称为Humonmous区。比如堆内存是4G每个Region是2M只要一个大对象超过了1M就被放入Humongous区如果对象过大会横跨多个Region。7.多次回收之后会出现很多Old老年代区此时总堆占有率达到阈值时会触发混合回收MixedGC。回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法来完成。