只有光头才能变强html
JVM在准备面试的时候就有看了,一直没时间写笔记。如今到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书。java
学习JVM的目的也很简单:面试
(图片来源:https://zhuanlan.zhihu.com/p/25511795,侵删)算法
声明:全文默认指的是HotSpot VMbootstrap
如今我有一个JavaBean:segmentfault
public class Java3y { // 姓名 private String name; // 年龄 private int age; //.....各类get/set方法/toString }
一个测试类:缓存
public class Java3yTest { public static void main(String[] args) { Java3y java3y = new Java3y(); java3y.setName("Java3y"); System.out.println(java3y); } }
咱们在初学的时候确定用过javac
来编译.java
文件代码,用过java
命令来执行编译后生成的.class
文件。安全
Java源文件:服务器
在使用IDE点击运行的时候其实就是将这两个命令结合起来了(编译并运行),方便咱们开发。微信
生成class文件
解析class文件获得结果
.java
文件是由Java源码编译器(上述所说的javac.exe)来完成,流程图以下所示:
Java源码编译由如下三个过程组成:
语法糖能够看作是编译器实现的一些“小把戏”,这些“小把戏”可能会使得效率“大提高”。
最值得说明的就是泛型了,这个语法糖能够说咱们是常常会使用到的!
有了泛型这颗语法糖之后:
了解泛型更多的知识:
至此,咱们经过javac.exe
编译器编译咱们的.java
源代码文件生成出.class
文件了!
这些.class
文件很明显是不能直接运行的,它不像C语言(编译cpp后生成exe文件直接运行)
这些.class
文件是交由JVM来解析运行!
如今咱们例子中生成的两个.class
文件都会直接被加载到JVM中吗??
虚拟机规范则是严格规定了有且只有5种状况必须当即对类进行“初始化”(class文件加载到JVM中):
因此说:
class文件是经过类的加载器装载到jvm中的!
Java默认有三种类加载器:
各个加载器的工做责任:
工做过程:
其实这就是所谓的双亲委派模型。简单来讲:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
好处:
特别说明:
java.lang.Class
类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。加载器加载到jvm中,接下来其实又分了好几个步骤:
通常咱们可能会想:JVM在加载了这些class文件之后,针对这些字节码,逐条取出,逐条执行-->解析器解析。
但若是是这样的话,那就太慢了!
咱们的JVM是这样实现的:
热点代码解释:1、屡次调用的方法。2、屡次执行的循环体
使用热点探测来检测是否为热点代码,热点探测有两种方式:
目前HotSpot使用的是计数器的方式,它为每一个方法准备了两类计数器:
按咱们程序来走,咱们的Java3yTest.class
文件会被AppClassLoader加载器(由于ExtClassLoader和BootStrap加载器都不会加载它[双亲委派模型])加载到JVM中。
随后发现了要使用Java3y这个类,咱们的Java3y.class
文件会被AppClassLoader加载器(由于ExtClassLoader和BootStrap加载器都不会加载它[双亲委派模型])加载到JVM中
详情参考:
扩展阅读:
在类加载检查经过后,接下来虚拟机将为新生对象分配内存。
首先咱们来了解一下JVM的内存模型的怎么样的:
简单看了一下内存模型,简单看看每一个区域究竟存储的是什么(干的是什么):
我来宏观简述一下咱们的例子中的工做流程:
java.exe
运行Java3yTest.class
,随后被加载到JVM中,元空间存储着类的信息(包括类的名称、方法信息、字段信息..)。Java3y java3y = new Java3y();
就是让JVM建立一个Java3y对象,可是这时候方法区中没有Java3y类的信息,因此JVM立刻加载Java3y类,把Java3y类的类型信息放到方法区中(元空间)java3y.setName("Java3y");
的时候,JVM根据java3y引用找到Java3y对象,而后根据Java3y对象持有的引用定位到方法区中Java3y类的类型信息的方法表,得到setName()
函数的字节码的地址setName()
函数建立栈帧,开始运行setName()
函数从微观上其实还作了不少东西,正如上面所说的类加载过程(加载-->链接(验证,准备,解析)-->初始化),在类加载完以后jvm为其分配内存(分配内存中也作了很是多的事)。因为这些步骤并非一步一步往下走,会有不少的“混沌bootstrap”的过程,因此很难描述清楚。
参考资料:
在写这篇文章的时候,本来觉得我对String s = "aaa";
相似这些题目已是不成问题了,直到我遇到了String.intern()
这样的方法与诸如String s1 = new String("1") + new String("2");
混合一块儿用的时候
首先我是先阅读了美团技术团队的这篇文章:https://tech.meituan.com/in_depth_understanding_string_intern.html---深刻解析String#intern
嗯,而后就懵逼了。我摘抄一下他的例子:
public static void main(String[] args) { String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); }
打印结果是
调换一下位置后:
public static void main(String[] args) { String s = new String("1"); String s2 = "1"; s.intern(); System.out.println(s == s2); String s3 = new String("1") + new String("1"); String s4 = "11"; s3.intern(); System.out.println(s3 == s4); }
打印结果为:
文章中有很详细的解析,但我简单阅读了几回之后仍是很懵逼。因此我知道了本身的知识点还存在漏洞,后面阅读了一下R大以前写过的文章:
看完了以后,就更加懵逼了。
后来,在zhihu上看到了这个回答:
结合网上资料和本身的思考,下面整理一下对常量池的理解~~
针对于jdk1.7以后:
常量池存储的是:
常量池(Constant Pool Table),用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池中存放--->来源:深刻理解Java虚拟机 JVM高级特性与最佳实践(第二版)
如今咱们的运行时常量池只是换了一个位置(本来来方法区,如今在堆中),但能够明确的是:类加载后,常量池中的数据会在运行时常量池中存放!
HotSpot VM里,记录interned string的一个全局表叫作StringTable,它本质上就是个HashSet
。注意 它只存储对java.lang.String实例的引用,而不存储String对象的内容
字符串常量池只存储引用,不存储内容!
再来看一下咱们的intern方法:
* When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned.
原本打算写注释的方式来解释的,但好像挺难说清楚的。我仍是画图吧...
public static void main(String[] args) { String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2);// false System.out.println("-----------关注公众号:Java3y-------------"); }
第一句:String s = new String("1");
第二句:s.intern();
发现字符串常量池中已经存在"1"字符串对象,直接返回字符串常量池中对堆的引用(但没有接收)-->此时s引用仍是指向着堆中的对象
第三句:String s2 = "1";
发现字符串常量池已经保存了该对象的引用了,直接返回字符串常量池对堆中字符串的引用
很容易看到,两条引用是不同的!因此返回false。
public static void main(String[] args) { System.out.println("-----------关注公众号:Java3y-------------"); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); // true }
第一句:String s3 = new String("1") + new String("1");
注意:此时"11"对象并无在字符串常量池中保存引用。
第二句:s3.intern();
发现"11"对象并无在字符串常量池中,因而将"11"对象在字符串常量池中保存当前字符串的引用,并返回当前字符串的引用(但没有接收)
第三句:String s4 = "11";
发现字符串常量池已经存在引用了,直接返回(拿到的也是与s3相同指向的引用)
根据上述所说的:最后会返回true~~~
若是仍是不太清楚的同窗,能够试着接收一下intern()
方法的返回值,再看看上述的图,应该就能够理解了。
下面的就由各位来作作,看是否是掌握了:
public static void main(String[] args) { String s = new String("1"); String s2 = "1"; s.intern(); System.out.println(s == s2);//false String s3 = new String("1") + new String("1"); String s4 = "11"; s3.intern(); System.out.println(s3 == s4);//false }
还有:
public static void main(String[] args) { String s1 = new String("he") + new String("llo"); String s2 = new String("h") + new String("ello"); String s3 = s1.intern(); String s4 = s2.intern(); System.out.println(s1 == s3);// true System.out.println(s1 == s4);// true }
能够说GC垃圾回收是JVM中一个很是重要的知识点,应该很是详细去讲解的。但在我学习的途中,我已经发现了有很好的文章去讲解垃圾回收的了。
因此,这里我只简单介绍一下垃圾回收的东西,详细的能够到下面的面试题中查阅和最后给出相关的资料阅
读吧~
在C++中,咱们知道建立出的对象是须要手动去delete掉的。咱们Java程序运行在JVM中,JVM能够帮咱们“自动”回收不须要的对象,对咱们来讲是十分方便的。
虽说“自动”回收了咱们不须要的对象,但若是咱们想变强,就要变秃..不对,就要去了解一下它到底是怎么干的,理论的知识有哪些。
首先,JVM回收的是垃圾,垃圾就是咱们程序中已是不须要的了。垃圾收集器在对堆进行回收前,第一件事情就是要肯定这些对象之中哪些还“存活”着,哪些已经“死去”。判断哪些对象“死去”经常使用有两种方式:
如今已经能够判断哪些对象已经“死去”了,咱们如今要对这些“死去”的对象进行回收,回收也有好几种算法:
(这些算法详情可看下面的面试题内容)~
不管是可达性分析算法,仍是垃圾回收算法,JVM使用的都是准确式GC。JVM是使用一组称为OopMap的数据结构,来存储全部的对象引用(这样就不用遍历整个内存去查找了,空间换时间)。
而且不会将全部的指令都生成OopMap,只会在安全点上生成OopMap,在安全区域上开始GC。
上面所讲的垃圾收集算法只能算是方法论,落地实现的是垃圾收集器:
上面这些收集器大部分是能够互相组合使用的
不少作过JavaWeb项目(ssh/ssm)这样的同窗可能都会遇到过OutOfMemory这样的错误。通常解决起来也很方便,在启动的时候加个参数就好了。
上面也说了不少关于JVM的东西--->JVM对内存的划分啊,JVM各类的垃圾收集器啊。
内存的分配的大小啊,使用哪一个收集器啊,这些均可以由咱们根据需求,现实状况来指定的,这里就不详细说了,等真正用到的时候才回来填坑吧~~~~
参考资料:
拿些常见的JVM面试题来作作,加深一下理解和查缺补漏:
题目来源:
根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
具体可能会聊聊jdk1.7之前的PermGen(永久代),替换成Metaspace(元空间)
图片来源:http://www.javashuo.com/article/p-smjktxqf-nt.html
参考资料:
内存泄漏的缘由很简单:
常见的内存泄漏例子:
public static void main(String[] args) { Set set = new HashSet(); for (int i = 0; i < 10; i++) { Object object = new Object(); set.add(object); // 设置为空,这对象我再也不用了 object = null; } // 可是set集合中还维护这obj的引用,gc不会回收object对象 System.out.println(set); }
解决这个内存泄漏问题也很简单,将set设置为null,那就能够避免上诉内存泄漏问题了。其余内存泄漏得一步一步分析了。
内存泄漏参考资料:
内存溢出的缘由:
解决:
参考资料:
这里的线程栈应该指的是虚拟机栈吧...
JVM规范让每一个Java线程拥有本身的独立的JVM栈,也就是Java方法的调用栈。
当方法调用的时候,会生成一个栈帧。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操做数栈、动态链接和方法返回地址等信息
线程运行过程当中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素。
经过jstack工具查看线程状态
参考资料:
这题就依据full GC的触发条件来作:
System.gc()
方法的调用双亲委托模型的重要用途是为了解决类载入过程当中的安全性问题。
java.lang.Object
的类,想借此欺骗JVM。如今他要使用自定义ClassLoader
来加载本身编写的java.lang.Object
类。Bootstrap ClassLoader
的路径下找到java.lang.Object
类,并载入它Java的类加载是否必定遵循双亲委托模型?
参考资料:
检验一下是否是真懂了:
class Dervied extends Base { private String name = "Java3y"; public Dervied() { tellName(); printName(); } public void tellName() { System.out.println("Dervied tell name: " + name); } public void printName() { System.out.println("Dervied print name: " + name); } public static void main(String[] args) { new Dervied(); } } class Base { private String name = "公众号"; public Base() { tellName(); printName(); } public void tellName() { System.out.println("Base tell name: " + name); } public void printName() { System.out.println("Base print name: " + name); } }
输出数据:
Dervied tell name: null Dervied print name: null Dervied tell name: Java3y Dervied print name: Java3y
第一次作错的同窗点个赞,加个关注不过度吧(hahaha
当young gen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).
这题不是很明白意思(水平有限...若是知道这题的意思可在评论区留言呀~~)
YGC和FGC是什么
何时执行YGC和FGC
System.gc()
,ygc时的悲观策略, dump live的内存信息时(jmap –dump:live),都会执行full gcGC最基础的算法有三种:
具体:
图来源于《深刻理解Java虚拟机:JVM高级特效与最佳实现》,图中两个收集器之间有连线,说明它们能够配合使用.
stackoverflow错误主要出现:
permgen space错误(针对jdk以前1.7版本):
总的来讲,JVM在初级的层面上仍是偏理论多,可能要作具体的东西才会有更深的体会。这篇主要是入个门吧~
这篇文章懒懒散散也算把JVM比较重要的知识点理了一遍了,后面打算学学,写写SpringCloud的东西。
参考资料:
若是文章有错的地方欢迎指正,你们互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同窗,能够关注微信公众号:Java3y。为了你们方便,刚新建了一下qq群:742919422,你们也能够去交流交流。谢谢支持了!但愿能多介绍给其余有须要的朋友
文章的目录导航: