黑马程序员技术交流社区

标题: 【上海校区】学习JVM是如何从入门到放弃的? [打印本页]

作者: 不二晨    时间: 2018-7-17 10:13
标题: 【上海校区】学习JVM是如何从入门到放弃的?
JVM在准备面试的时候就有看了,一直没时间写笔记。现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书。
学习JVM的目的也很简单:


(图片来源:zhuanlan.zhihu.com/p/25511795,侵删)
声明:全文默认指的是HotSpot VM
一、简单聊聊JVM1.1先来看看简单的Java程序现在我有一个JavaBean:
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文件得到结果


1.2编译过程.java文件是由Java源码编译器(上述所说的javac.exe)来完成,流程图如下所示:


Java源码编译由以下三个过程组成:


1.2.1编译时期-语法糖
语法糖可以看做是编译器实现的一些“小把戏”,这些“小把戏”可能会使得效率“大提升”。
最值得说明的就是泛型了,这个语法糖可以说我们是经常会使用到的!
有了泛型这颗语法糖以后:
了解泛型更多的知识:
1.3JVM实现跨平台至此,我们通过javac.exe编译器编译我们的.java源代码文件生成出.class文件了!


这些.class文件很明显是不能直接运行的,它不像C语言(编译cpp后生成exe文件直接运行)
这些.class文件是交由JVM来解析运行


1.4class文件和JVM的恩怨情仇1.4.1类的加载时机现在我们例子中生成的两个.class文件都会直接被加载到JVM中吗??
虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(class文件加载到JVM中):
所以说:
1.4.2如何将类加载到jvmclass文件是通过类的加载器装载到jvm中的!
Java默认有三种类加载器


各个加载器的工作责任:
工作过程:
其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上
好处:
特别说明:
1.4.2类加载详细过程加载器加载到jvm中,接下来其实又分了好几个步骤


1.4.3JIT即时编辑器一般我们可能会想:JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行-->解析器解析。
但如果是这样的话,那就太慢了!
我们的JVM是这样实现的:
热点代码解释:一、多次调用的方法。二、多次执行的循环体
使用热点探测来检测是否为热点代码,热点探测有两种方式:
目前HotSpot使用的是计数器的方式,它为每个方法准备了两类计数器:


1.4.4回到例子中按我们程序来走,我们的Java3yTest.class文件会被AppClassLoader加载器(因为ExtClassLoader和BootStrap加载器都不会加载它[双亲委派模型])加载到JVM中。
随后发现了要使用Java3y这个类,我们的Java3y.class文件会被AppClassLoader加载器(因为ExtClassLoader和BootStrap加载器都不会加载它[双亲委派模型])加载到JVM中


详情参考:
扩展阅读:
1.5类加载完以后JVM干了什么?在类加载检查通过后,接下来虚拟机将为新生对象分配内存
1.5.1JVM的内存模型首先我们来了解一下JVM的内存模型的怎么样的:


简单看了一下内存模型,简单看看每个区域究竟存储的是什么(干的是什么):
1.5.2例子中的流程

我来宏观简述一下我们的例子中的工作流程:
从微观上其实还做了很多东西,正如上面所说的类加载过程(加载-->连接(验证,准备,解析)-->初始化),在类加载完之后jvm为其分配内存(分配内存中也做了非常多的事)。由于这些步骤并不是一步一步往下走,会有很多的“混沌bootstrap”的过程,所以很难描述清楚。
参考资料:
1.6简单聊聊各种常量池在写这篇文章的时候,原本以为我对String s = "aaa";类似这些题目已经是不成问题了,直到我遇到了String.intern()这样的方法与诸如String s1 = new String("1") + new String("2");混合一起用的时候
首先我是先阅读了美团技术团队的这篇文章:tech.meituan.com/in_depth_un…---深入解析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上看到了这个回答:
结合网上资料和自己的思考,下面整理一下对常量池的理解~~
1.6.1各个常量池的情况针对于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. 复制代码1.6.2解析题目本来打算写注释的方式来解释的,但好像挺难说清楚的。我还是画图吧...
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    }复制代码1.7GC垃圾回收可以说GC垃圾回收是JVM中一个非常重要的知识点,应该非常详细去讲解的。但在我学习的途中,我已经发现了有很好的文章去讲解垃圾回收的了。
所以,这里我只简单介绍一下垃圾回收的东西,详细的可以到下面的面试题中查阅和最后给出相关的资料阅读吧~
1.7.1JVM垃圾回收简单介绍在C++中,我们知道创建出的对象是需要手动去delete掉的。我们Java程序运行在JVM中,JVM可以帮我们“自动”回收不需要的对象,对我们来说是十分方便的。
虽然说“自动”回收了我们不需要的对象,但如果我们想变强,就要变秃..不对,就要去了解一下它究竟是怎么干的,理论的知识有哪些。
首先,JVM回收的是垃圾,垃圾就是我们程序中已经是不需要的了。垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。判断哪些对象“死去”常用有两种方式:


现在已经可以判断哪些对象已经“死去”了,我们现在要对这些“死去”的对象进行回收,回收也有好几种算法:
(这些算法详情可看下面的面试题内容)~
无论是可达性分析算法,还是垃圾回收算法,JVM使用的都是准确式GC。JVM是使用一组称为OopMap的数据结构,来存储所有的对象引用(这样就不用遍历整个内存去查找了,空间换时间)。并且不会将所有的指令都生成OopMap,只会在安全点上生成OopMap,在安全区域上开始GC。
上面所讲的垃圾收集算法只能算是方法论,落地实现的是垃圾收集器
上面这些收集器大部分是可以互相组合使用


1.8JVM参数与调优很多做过JavaWeb项目(ssh/ssm)这样的同学可能都会遇到过OutOfMemory这样的错误。一般解决起来也很方便,在启动的时候加个参数就行了。
上面也说了很多关于JVM的东西--->JVM对内存的划分啊,JVM各种的垃圾收集器啊。
内存的分配的大小啊,使用哪个收集器啊,这些都可以由我们根据需求,现实情况来指定的,这里就不详细说了,等真正用到的时候才回来填坑吧~~~~
参考资料:
二、JVM面试题拿些常见的JVM面试题来做做,加深一下理解和查缺补漏
题目来源:
2.1详细jvm内存模型根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。


具体可能会聊聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)


图片来源:blog.csdn.net/tophawk/art…
参考资料:
2.2讲讲什么情况下回出现内存溢出,内存泄漏?内存泄漏的原因很简单:
常见的内存泄漏例子:
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,那就可以避免上诉内存泄漏问题了。其他内存泄漏得一步一步分析了。
内存泄漏参考资料:
内存溢出的原因:
解决:
参考资料:
2.3说说线程栈
这里的线程栈应该指的是虚拟机栈吧...
JVM规范让每个Java线程拥有自己的独立的JVM栈,也就是Java方法的调用栈。
当方法调用的时候,会生成一个栈帧。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息
线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素
通过jstack工具查看线程状态
参考资料:
2.4JVM 年轻代到年老代的晋升过程的判断条件是什么呢?2.5JVM 出现 fullGC 很频繁,怎么去线上排查问题这题就依据full GC的触发条件来做:


2.6类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?双亲委托模型的重要用途是为了解决类载入过程中的安全性问题
Java的类加载是否一定遵循双亲委托模型?
参考资料:
2.7类的实例化顺序检验一下是不是真懂了:
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: nullDervied print name: nullDervied tell name: Java3yDervied print name: Java3y复制代码第一次做错的同学点个赞,加个关注不过分吧(hahaha
2.8JVM垃圾回收机制,何时触发MinorGC等操作当young gen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).
2.9JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的这题不是很明白意思(水平有限...如果知道这题的意思可在评论区留言呀~~)
YGC和FGC是什么
什么时候执行YGC和FGC
2.10各种回收算法GC最基础的算法有三种:
具体:
2.11各种回收器,各自优缺点,重点CMS、G1图来源于《深入理解Java虚拟机:JVM高级特效与最佳实现》,图中两个收集器之间有连线,说明它们可以配合使用.


2.12stackoverflow错误,permgen space错误stackoverflow错误主要出现:
permgen space错误(针对jdk之前1.7版本):
三、总结总的来说,JVM在初级的层面上还是偏理论多,可能要做具体的东西才会有更深的体会。这篇主要是入个门吧~
这篇文章懒懒散散也算把JVM比较重要的知识点理了一遍了,后面打算学学,写写SpringCloud的东西。
参考资料:


作者:Java3y
链接:https://juejin.im/post/5b45ef49f265da0f5140489c




作者: 小怪兽暖阳    时间: 2018-7-17 12:23
学了一个月的萌新表示基本看不懂,我是不是可以放弃了

作者: 不二晨    时间: 2018-7-17 13:38
奈斯
作者: 不二晨    时间: 2018-7-17 13:39
小怪兽暖阳 发表于 2018-7-17 12:23
学了一个月的萌新表示基本看不懂,我是不是可以放弃了

这个比较专业化的了,不着急,先学基础,打好基础
作者: 不二晨    时间: 2018-7-18 10:58
奈斯,优秀
作者: 小影姐姐    时间: 2018-7-18 11:22
牛牛牛!
作者: 吴琼老师    时间: 2018-7-18 14:17

作者: 吴琼老师    时间: 2018-7-19 16:40

作者: 摩西摩西OvO    时间: 2018-7-23 13:35

作者: 摩西摩西OvO    时间: 2018-7-26 09:19





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2