深刻理解JVM虚拟机总结

如下总结不保证全对,若有错误,还望可以指出。谢谢前端

相关代码实如今个人GitHub里:git

github.com/h2pl程序员

喜欢的话麻烦star一下哈github

本系列技术文章首发于个人我的博客:面试

h2pl.github.io算法

更多关于Java后端学习的内容请到个人CSDN博客上查看:bootstrap

blog.csdn.net/a724888后端

JVM介绍和源码

首先JVM是一个虚拟机,当你安装了jre,它就包含了jvm环境。JVM有本身的内存结构,字节码执行引擎,所以class字节码才能在jvm上运行,除了Java之外,Scala,groovy等语言也能够编译成字节码然后在jvm中运行。JVM是用c开发的。数组

JVM内存模型

内存模型老生常谈了,主要就是线程共享的堆区,方法区,本地方法栈。还有线程私有的虚拟机栈和程序计数器。安全

堆区存放全部对象,每一个对象有一个地址,Java类jvm初始化时加载到方法区,然后会在堆区中生成一个Class<?>对象,来负责这个类全部实例的实例化。

栈区存放的是栈帧结构,栈帧是一段内存空间,包括参数列表,返回地址,局部变量表等,局部变量表由一堆slot组成,slot的大小固定,根据变量的数据类型决定须要用到几个slot。

方法区存放类的元数据,将原来的字面量转换成引用,固然,方法区也提供常量池,常量池存放-128到127的数字类型的包装类。 字符串常量池则会存放使用intern的字符串变量。

JVM OOM和内存泄漏

这里指的是oom和内存泄漏这类错误。

oom通常分为三种,堆区内存溢出,栈区内存溢出以及方法区内存溢出。

堆内存溢出主要缘由是建立了太多对象,好比一个集合类死循环添加一个数,此时设置jvm参数使堆内存最大值为10m,一会就会报oom异常。

栈内存溢出主要与栈空间和线程有关,由于栈是线程私有的,若是建立太多线程,内存值超过栈空间上限,也会报oom。

方法区内存溢出主要是因为动态加载类的数量太多,或者是不断建立一个动态代理,用不了多久方法区内存也会溢出,会报oom,这里在1.7以前会报permgem oom,1.8则会报meta space oom,这是由于1.8中删除了堆中的永久代,转而使用元数据区。

内存泄漏通常是由于对象被引用没法回收,好比一个集合中存着不少对象,可能你在外部代码把对象的引用置空了,可是因为对象还被集合给引用着,因此没法被回收,致使内存泄漏。测试也很简单,就在集合里添加对象,添加完之后把引用置空,循环操做,一会就会出现oom异常,缘由是内存泄漏太多了,致使没有空间分配新的对象。

常见调试工具

命令行工具备jstack jstat jmap 等,jstack能够跟踪线程的调用堆栈,以便追踪错误缘由。

jstat能够检查jvm的内存使用状况,gc状况以及线程状态等。

jmap用于把堆栈快照转储到文件系统,而后能够用其余工具去排查。

visualvm是一款很不错的gui调试工具,能够远程登陆主机以便访问其jvm的状态并进行监控。

class文件结构

class文件结构比较复杂,首先jvm定义了一个class文件的规则,而且让jvm按照这个规则去验证与读取。

开头是一串魔数,而后接下来会有各类不一样长度的数据,经过class的规则去读取这些数据,jvm就能够识别其内容,最后将其加载到方法区。

JVM的类加载机制

jvm的类加载顺序是bootstrap类加载器,extclassloader加载器,最后是appclassloader用户加载器,分别加载的是jdk/bin ,jdk/ext以及用户定义的类目录下的类(通常经过ide指定),通常核心类都由bootstrap和ext加载器来加载,appclassloader用于加载本身写的类。

双亲委派模型,加载一个类时,首先获取当前类加载器,先找到最高层的类加载器bootstrap让他尝试加载,他若是加载不了再让ext加载器去加载,若是他也加载不了再让appclassloader去加载。这样的话,确保一个类型只会被加载一次,而且以高层类加载器为准,防止某些类与核心类重复,产生错误。

defineclass findclass和loadclass

类加载classloader中有两个方法loadclass和findclass,loadclass听从双亲委派模型,先调用父类加载的loadclass,若是父类和本身都没法加载该类,则会去调用findclass方法,而findclass默认实现为空,若是要自定义类加载方式,则能够重写findclass方法。

常见使用defineclass的状况是从网络或者文件读取字节码,而后经过defineclass将其定义成一个类,而且返回一个Class<?>对象,说明此时类已经加载到方法区了。固然1.8之前实现方法区的是永久代,1.8之后则是元空间了。

JVM虚拟机字节码执行引擎

jvm经过字节码执行引擎来执行class代码,他是一个栈式执行引擎。这部份内容比较高深,在这里就不献丑了。

编译期优化和运行期优化

编译期优化主要有几种

1 泛型的擦除,使得泛型在编译时变成了实际类型,也叫伪泛型。

2 自动拆箱装箱,foreach循环自动变成迭代器实现的for循环。

3 条件编译,好比if(true)直接可得。

运行期优化主要有几种

1 JIT即时编译

Java既是编译语言也是解释语言,由于须要编译代码生成字节码,然后经过解释器解释执行。

可是,有些代码因为常常被使用而成为热点代码,每次都编译太过费时费力,干脆直接把他编译成本地代码,这种方式叫作JIT即时编译处理,因此这部分代码能够直接在本地运行而不须要经过jvm的执行引擎。

2 公共表达式擦除,就是一个式子在后面若是没有被修改,在后面调用时就会被直接替换成数值。

3 数组边界擦除,方法内联,比较偏,意义不大。

4 逃逸分析,用于分析一个对象的做用范围,若是只局限在方法中被访问,则说明不会逃逸出方法,这样的话他就是线程安全的,不须要进行并发加锁。

1

JVM的垃圾回收

1 GC算法:中止复制,存活对象少时适用,缺点是须要两倍空间。标记清除,存活对象多时适用,可是容易产生随便。标记整理,存活对象少时适用,须要移动对象较多。

2 GC分区,通常GC发生在堆区,堆区可分为年轻代,老年代,之前有永久代,如今没有了。

年轻代分为eden和survior,新对象分配在eden,当年轻代满时触发minor gc,存活对象移至survivor区,而后两个区互换,等待下一场gc, 当对象存活的阈值达到设定值时进入老年代,大对象也会直接进入老年代。

老年代空间较大,当老年代空间不足以存放年轻代过来的对象时,开始进行full gc。同时整理年轻代和老年代。 通常年轻代使用中止复制,老年代使用标记清除。

3 垃圾收集器

serial串行

parallel并行

它们都有年轻代与老年代的不一样实现。

而后是scanvage收集器,注重吞吐量,能够本身设置,不过不注重延迟。

cms垃圾收集器,注重延迟的缩短和控制,而且收集线程和系统线程能够并发。

cms收集步骤主要是,初次标记gc root,而后停顿进行并发标记,然后处理改变后的标记,最后停顿进行并发清除。

g1收集器和cms的收集方式相似,可是g1将堆内存划分红了大小相同的小块区域,而且将垃圾集中到一个区域,存活对象集中到另外一个区域,而后进行收集,防止产生碎片,同时使分配方式更灵活,它还支持根据对象变化预测停顿时间,从而更好地帮用户解决延迟等问题。

JVM的锁优化

在Java并发中讲述了synchronized重量级锁以及锁优化的方法,包括轻量级锁,偏向锁,自旋锁等。详细内容能够参考个人专栏:Java并发技术指南

微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字能够获取对应的免费学习资料。

相关文章
相关标签/搜索