这,仅是我学习过程中记录的笔记。确定了一个待研究的主题,对这个主题进行全方面的剖析。笔记是用来方便我回顾与学习的,欢迎大家与我进行交流沟通,共同成长。不止是技术。
2020年02月06日22:43:09-记录学习过程
终于开始了。在学习这个之前,看了zhanglong老师的java8和springboot
迫不及待了。先开始吧。
听说之前还有netty和kotlin。学习风格就是,每一门课程之前,前两节课不进入主题,讲方法论。
从他人身上学习优点。加强自己的学习。从人去学习,从事去学习。我们只有亲身经历一件事情,才会产生自己的想法。从事情学习付出的成本会相对的高一点。只有一件事,你失败了,才会发现你存在什么问题。从过程中吸收一点经验,指导着你未来学习前进的方向。从人去学习来说,不是你自己亲身经历的,要学习辨别能力。为什么大家在看书的时候,看书的印象不如你自己操作的印象深刻呢?这些都是值得去思考的。更为高效的方式,还是看别人的故事,揣摩自己的人生。将别人拥有的技能转换成自己的技能,这样才是高效的学习。
学习的过程中,一定要做到两点。
印象笔记,有道云笔记,小小的工具,可能给你生活带来很大的改变。甚至改变你的一生。讲课是一件非常辛苦的事情。平时的工作中就深有体会。
如果去学习JVM,每一个来学习JVM的人,都渴望成功。每一个Java开发人员的终极目标都是在日常生活中深入理解JVM的运行原理。JVM和平时的应用框架明显的区别,应用框架学习之后,可以直接拿来写项目了,就可以运行起来看到helloworld。然而对于JVM,是一个特别枯燥的事情。涵盖的内容太多了。本次根据java8来学习。必须要笔记。因为一扭头就会忘记。绝对没有速成的,突击的。有节奏,有计划的去学习。关于这门技术,范围太广,从通用的层面来进行学习。
推荐一些学习资料:一边学习视频,一边学习资料;
《深入理解Java虚拟机》,《深入Java虚拟机》,R大
可能会遇到的问题:
开发过程中遇到问题是常态。如果遇到了JVM崩溃,就算你拿到了日志,你也不能定位到问题是什么。对于原理,对于基础的学习,能够增强我们的自信心。
介绍:JVM是一个令人望而却步的领域,因为它博大精深,涉及到的内容与知识点非常之多。虽然Java开发者每天都在使用JVM,但对其有所研究并且研究深入的人却少之又少。然而,JVM的重要性却又是不言而喻的。基于JVM的各种动态与静态语言生态圈已经异常繁荣了,对JVM的运行机制有一定的了解不但可以提升我们的竞争力,还可以让我们在面对问题时能够沉着应对,加速问题的解决速度;同时还能够增强我们的自信心,让我们更加游刃有余。
以前都不知道这些工具的存在:
jConsole
Jvusualvm
jmap
在Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的。
(类型,并不代表类产生的对象,而是类本身。类型是在程序运行期间生成出来的,runtime)
提供了更大的灵活性,增加了更多的可能性
(为有创意的开发者提供了很多的功能。)
从代码来理解:classTest{publicstaticinta=1;}//我们程序中给定的是publicstaticinta=1;//但是在加载过程中的步骤如下:1.加载阶段编译文件为class文件,然后通过类加载,加载到JVM2.连接阶段第一步(验证):确保Class类文件没问题第二步(准备):先初始化为a=0。(因为你int类型的初始值为0)第三步(解析):将引用转换为直接引用3.初始化阶段:通过此解析阶段,把1赋值为变量a4.使用阶段我们平时使用的对象,操作,方法调用,等等都是使用阶段5.卸载阶段类在卸载之后,就不能够继续new对象,平时开发很少接触到这个卸载阶段。比如-OSGI技术会使用到卸载图解:
类的加载指的是将类的.class文件中二进制数据读入到内存中,将其放在运行时数据区内的方法去内,然后再内存中创建一个java.lang.Class对象(规范并未说明Class对象谓语哪里,HotSpot虚拟机将其放在了方法去中)用来封装类在方法区内的数据结构
/*举例说明: 对于静态字段来说,只有直接定义了该字段的类才会被初始化; 当一个类在初始化是,要求其父类全部都已经初始化完毕了; -XX:+TraceClassLoading,用于追种类的加载信息并打印出来。 所有的参数都是: -XX:+
jvm参数介绍:-XX:+,表示开启option选项-XX:+,表示关闭option选项-XX:+=表示将option选项的值设置为value
/*常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中,本质上,调用类并没有直接用用到定义常量的类,因此并不会触发定义常量的类的初始化。注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了。甚至,我们可以将MyParent2的Class文件删除*/publicclassMyTest2{publicstaticvoidmain(String[]args){System.out.println(MyParent2.str);}}classMyParent2{publicstaticfinalStringstr="helloworld";publicstaticfinalshorts=127;publicstaticfinalinta=3;publicstaticfinalintm=6;static{System.out.println("Myparent2staticblock");//这一行能输出吗?不会}}反编译javap-ccom.erwa.jvm.class.mytest2
反编译之后会有助记符。
ldc表示将int、float或是String类型的常量值从常量池中推送至栈顶。
bipush表示将单字节(-128~127)的常量值推送至栈顶。
sipush表示将短整型(-32767~32768)的常量值推送至栈顶。
inconst_1表示将int类型1推送至栈顶(inconst_m1~inconst_5)。
anewarray表示创建一个引用类型(如类,接口,数组)的数组,并将其引用值推至栈顶。
newarray表示创建一个指定的原始类型(如int,float,char等)的数组,并将其引用值推至栈顶。
类的加载
有两种类型的类加载器
类加载器并不需要等到某个类被“首次使用”时再加载它。
依次执行初始化语句:按照CLass类文件中的顺序加载静态方法和静态代码块。

重点介绍一下上图的概念。举例说明。
调用CLassLoader类的loadCLass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
下图:不是继承关系,是包含关系。
线程上下文类加载器作用:打破双亲委托机制。加载SPI提供的类。
一层一层的让父类去加载,最顶层父类不能加载往下数,依次类推。
能够成功加载自定义类加载器的系统类加载器被称为定义类加载器。
所有能够返回Class对象引用的类加载器都被称为初始类加载器。
publicclassMyTest13{psvm{ClassLoaderclassLoader=ClassLoader.getSystemClassLoader();sout(classLoader);while(null!=classLoader){classLoader=classLoader.getParent();sout(classLoader);}}}>输出结果:>Task:MyTest13.main()sun.misc.Launcher$APPClassLoader 应用类加载器sun.misc.Launcher$ExtClassLoader扩展类加载器null根类加载器//如何通过给定的字节码路径,打印出类的信息publicclassMyTest14{psvm{//上下文类加载器是由当前的线程提供的ClassLoaderclassLoader=Thread.currentThread().getContextClassLoader();StringresourceName="com/erwa/jvm/MyTest13.class";Enumeration
clazz.getClassLoader();
获得当前线程上下文的ClassLoader
Thread.currnetThread().getContextClassLoader();
获得系统的ClassLoader
CLassLoader.getSystemClassLoader();
获得调用者的ClassLoader
DriberManager.getCallerClassLoader();
扩展类加载器
应用类加载器
自定义类加载器
拒绝低效率的学习
输出结果一样,说明loader1和loader2成为了父子关系,在同一命名空间中。
类的卸载举例:
也可以通过一个小工具,来查看jvisualvm
系统类加载器:sun.boot.class.path(加载系统的包,包含jdk核心库里的类)
扩展类加载器:java.ext.dirs(加载扩展jar包中的类)
应用类加载器:java.class.path(加载你编写的类,编译后的类)
可以打印出各个加载器加载的目录:System.getProperty("上述的地址");
类加载器的双亲委托模型的好处:
publicclassMyTest22{static{sout("MyTest22initializer");}psvm{sout(MyTest22.class.getClassLoader());sout(MyTest1.class.getClassLoader());}}>1.此时的输出结果是什么?AppClassLoaderAppClassLoader>2.修改本地的扩展类加载器的加载路径。1.进入到当前编译的out路径2.java-Djava.ext.dirs=./com.erwa.jvm.MyTest22>3.此时的输出结果是什么?AppClassLoaderAppClassLoader4.还是一样的,为什么不是扩赞类加载器加载的呢?因为扩展类加载的类还不能是Class文件的形式,要以jar包的形式加载。>5.将当前的com打成jar包jarcvfcom.erwa.jvm>6.此时的输出结果是什么?AppClassLoaderExtClassLoader>7.java-Djava.ext.dirs=/com.erwa.jvm.MyTest22>8.此时的输出结果是什么?AppClassLoaderAppClassLoader关于命名空间的补充:
在运行期,一个Java类是由该类的完全限定名(binaryname,二进制名)和用于加载该类的定义类加载器共同确定的。
如果同样的名字(即相同的完全限定名)的类是由两个不同的加载器所加载,那么这些类就是不同的。即便.class文件的字节码完全一样,并且从相同的位置加载亦如此。
在Oracle的Hotspot实现中,系统属性sun.boot.class.path如果修改错了,则运行时会出错,信息下图:
类加载器的特例,数组。数组不是由类加载器的加载的,是由JVM加载的。
类加载器本身也是一个Java类,他们是怎么加载到JVM当中的呢?
他们是由启动类加载器加载的。BootStrapClassLoader加载的。他不是由Java代码写的,由C++代码编写的。启动的时候回自动加载出启动类加载器的实例。
内建与JVM中的启动类加载器会加载java.lang.ClassLoader以及其他的Java类加载器。
当JVM启动时,一块特殊的机器码会执行,它会加载类加载器与系统类加载器,这块特殊的机器码叫做启动类加载器(BootStarp)
启动类加载器并不是Java类,而其他的加载器则都是Java类
启动类加载器是特定于平台的机器指令,它负责开启整个加载过程
所有类加载器(除了启动类加载器)都被时限为Java类。不过,中国要有一个组件来加载第一个Java类加载器,从而让整个过程能够顺利的进行下去,加载第一个纯Java类加载器就是启动类加载器的职责。
启动类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的内容。
在自定义类加载器中加一个父类的构造方法,就可以正常的运行了。
用Java命令行的话,和idea里边执行的结果有时候类加载器的是不一样的。
因为idea也重新定义了类加载器的路径。
为什么扩展类加载的路径是java.ext.dirs源代码里边写的。
每一个类都会尝试使用自身的类加载器(即加载自身的类加载器)来加载其他的类(指的是所依赖的类)。
如:如果CLassx引用了ClassY,那么ClassX的类加载器就会去加载CLassY(前提是ClassY尚未被加载)
线程上下文类加载器(ContextCLassloader)
线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader();与setContextClassLoadert();分别用来获取和设置上下文类加载器。
如果没有通过setContextCLassLoader(ClassLoadercl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。
线程上下文类加载器的重要性:
SPI(ServiceProiderInterface)服务提供厂商;
父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的ClassLoader加载的类,这就改变了父ClassLoader不能使用子ClassLoader或者是其他没有直接父子关系的ClassLoader加载的类的情况,即这就改变了双亲委托模型。
线程上下文类加载器就是当前线程的CurrentClassLoader。
Thread.currentThread().setContextClassLoader(loader);publicclassMyTest24{psvm{sout(Thread.currentThread().getContextClassLoader());sout(Thread.class.getClassLoader());}}输出结果:AppClassLoadernull数据库厂商肯定会满足jdbc规范的接口。
Tomcat和spring的类加载机制和jdk的不同。
分析一下这两行代码底层是怎么实现的。已经都学习过了。
//初始化的作用//线程上下文类加载器的作用。//精辟。publicclassMyTest27{publicstaticvoidmain(String[]args)throwsException{//初始化,会加载类中华的静态方法和内部类。层层的,如果静态方法中调用静态类,会初始化内部的静态类Class.forName("com.mysql.jdbc.Driver");Connectionconnection=DriverManager.getConnection("jdbc:localhost:8080","username","password");}}2020年02月11日23:12:09。睡觉。
2020年02月12日17:07:52
总结回顾了一下之前学习的所有笔记中的内容。
论笔记的重要性。学习方式的升级。学习,遗忘,复习。笔记来做一个载体。
不要给后续留坑,前期欠的东西,以后总归是要还的。
技术学习与思维方式谈心
学习效率,学习方法,重要吗?
学习一个知识的时候太着急了。并不是一个视频学完了,这门技术就掌握了。
你学习的时候,目标是什么呢?把视频中讲解的内容给吸收理解了。
再次复习一下之前学习的规程。
类加载器学习到此结束。开启一个新的阶段。2020年02月12日21:31:10
.class字节码文件是跨平台的。
Java字节码文件的结构及组成部分的学习。
javap-ccom.erwa.jvm.MyTest1
Javap-verbosecom.erwa.jvm.MyTest1--返回class文件的冗余内容
.class字节码文件中的内容都是什么?待学习Hex_Fiend工具
上下两个表相互对应。
常量池中11中数据类型的结构总表:
Java是一个单继承,多实现的语言。
0x0002private
字段表结构:
code_length:之后的字节会转换成助记符
LineNumberTable:源代码和行号的对应关系。方便抛异常时,定义出错的位置。
在字节码文件中。this属性是默认当做第一个参数给传入进去的。
字节码查看工具的GitHub地址
idea也有插件。jclasslib
因为synchronized使用的方式有多种:
关于锁:能不用synchronized就不用。使用轻量级的lock。
stack=3;"最大栈深度"(对于方法栈,压栈弹出。)
locals=4;"成员变量的个数"(局部变量有:this,is,serverSocket,ex(同时只能有一个异常的参数))
agrs_size=1"参数的个数“(第一个默认的都是this)
goto语句是发生异常的时候进行跳转到catch的位置。
最后的一个any来说,是处理所有上边不可能处理的异常。是字节码生成的时候帮助我们生成的。
栈帧一种用于帮助虚拟机执行方法调用与方法执行的数据结构。他是独立于线程的,一个线程有自己的一个栈帧。不存在并发的情况。
栈帧本是一种数据结构,封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。
有些符号引用是在类加载阶段或是第一次使用时就会转换成直接引用,这种转换叫做静态解析;
另外一些符号引用则是在每次运行期转换为直接引用,这种转换叫做动态链接,这种体检为Java的多态性。
如:Animala=newCat();
a.sleep();//实际上应该调用Cat()的sleep();
a=newDog();
a.sleep();//实际上应该调用Dog()的sleep();
a=newTiger();
a.sleep();//实际上应该调用Tiger()的sleep();
但是在程序编译的时候,字节码能看到的是a调用的都是Animal的sleep();
应用了invokevirtual的概念。
结合上图,我用文字描述一下两个概念。看自己能否理解。
首先将2和3压入栈中。现在要做一个减法,3-2。需要先把2从栈中取出,3也从栈中取出,做完减法后,把1压入栈中。
/**方法的动态分派 方法的动态分派涉及到一个重要概念:方法接受者。 invokevirtual字节码指令的多态查找流程。 流程:找到操作数栈顶的第一个元素,对象引用的实际类型。 比较方法重载(overload)与方法重写(overwrite),我们可以得到这样一个结论: 方法重载是静态的,是编译期行为;方法重写是动态的,是运行期行为。*/publicclassMyTest6{psvm(){Fruitapple=newApple();Fruitorange=newOrange();apple.test();orange.test();apple=newOrange();apple.test();}}classFruit(){publicvoidtest(){sout("fruit");}}classAppleextendsFruit(){@Overridepublicvoidtest(){sout("Apple");}}classOrangeextendsFruit(){@Overridepublicvoidtest(){sout("Orange");}}输出结果为:appleorangeorangenew关键字的作用:1、开辟一个内存空间,2、调用构造方法3、赋值给局部变量
针对于方法调用动态分派的过程,虚拟机会在类的方法区建立一个虚方法表的数据结构。(virtualmethodtable,简称vtable),是在加载连接阶段完成的加载。
针对于invokeinterface指令来说,虚拟机会建立一个叫做接口范发表的数据结构。(interfacemethodtable,简称itable)
输出结果:
animalstr
Dogdate
子类和父类的相同的方法元素的索引号是一样的。这样会提高性能和效率。
2020年02月15日23:00:50。回顾完毕。晚安。
使用句柄的好处(引用永远指向他,引用不会跟着修改)
直接指针的好处(在压缩的时候效率比句柄的方式更高)
对应的JVM的运行信息:类,实例数等信息都可以在这里通过工具查看。
我这里jdk8带的jvisualvm打不开,没有跟着练习看一下。
Jvisualvm监控栈溢出过程。
Jconsole工具监控。也是Oracle自带的。
//自己编写一个可以产生死锁的代码。使用Jconsole检测死锁。publicclassMyTest3{publicstaticvoidmain(String[]args){newThread(A::method,"ThreadA").start();//使用lambda表达式启动线程.newThread(B::method,"ThreadB").start();}}classA{publicstaticsynchronizedvoidmethod(){//当方法持有锁时,线程过来时检测的锁是当前class的锁。System.out.println("methodfromA");try{Thread.sleep(5000);B.method();}catch(InterruptedExceptione){e.printStackTrace();}}}classB{publicstaticsynchronizedvoidmethod(){System.out.println("methodfromB");try{Thread.sleep(5000);A.method();}catch(InterruptedExceptione){e.printStackTrace();}}}ThreadA和ThreadB是main的子线程。//启动程序后,检测死锁,检测到了。结果如下图所示。使用Jconsole检测死锁的结果。
使用jvisualvm工具检测的结果:
使用jconsole来进行检测:
使用jvisoualvm进行检测:能够明确的看到元空间使用了多少M
存在两种工具。(图示化工具能看到的,命令行都可以得到。)
上图中的MC的作用:当前的元空间的容量
MU:元空间已经被使用的容量
jps命令:查看当前左右线程的pid.
jcmd和jps配合使用,查看JVM的多种信息。PID等
jcmd(从jdk1.7开始新增的命令)命令介绍:
jstackpid:查看或者导出当前线程的堆栈信息。
命令行:jps:查看PID
jcmd和jps配合使用,查看JVM的多种信息
jmc:JAVAMissionConreol
jfr:javaflightrecodrding飞行记录器
我本地不能打开。
功能和visualvm类似。涉及到了OQL对象查询语言,随用随查即可。
2020年02月17日20:58:02复习结束。开始新的课程啦。
垃圾回收最主要的是在堆中进行的。--了解了垃圾回收的原理,才能写出来更高效的代码。
个人理解:
灰色区域为线程共享的,白色区域为线程隔离。
方法区:存放元数据信息。常量池空间就在这个里边。
本地方法栈:存放navtion的,非Java代表编写的本地方法。
Java虚拟机栈(栈帧):存放对象的引用
堆:创建的对象存储在这里边。
什么是无法解决对象循环引用的问题。
已经没有外部的引用引用这两个对象了,是一个完全孤立的系统,但是这个两个对象又相互引用。所以这两个对象一直无法被回收。
每一种算法都有自己的好处与缺点
新生代,一般使用复制算法。
老年代,一般使用标记算法。
(选取算法的根据是1、生命周期2、有一个老年代作为分配担保的后盾)
上图中有引用的如下图所示,绿色的是垃圾回收时不会回收的,红色的是被回收的内容。
执行完一次垃圾回收之后,结果如下图所示。
复制后的样子:
垃圾回收后的样子
示意图:
2020年02月17日22:05:23这个时候发生了一个有趣的问题,原本把笔记全部给记录到一块,现在地下的时候卡的要死,现在重新创建了一个文件,完全没有一点问题了。
永久代从jdk8之后就被替换成了元空间。用来存放元信息。
强引用;软引用;弱引用;虚引用
scavenge:打扫
Full:完全的。(会导致FW的出现。占用业务线程。要在开发中完全避免这种GC);
serial:连续的、
parallel:平行的、scavenge:打扫
下图中的names的定义,定义为全局变量,但是只使用了一次。全局变量不会被立马被回收,但是局部变量会立马被回收的。
UseParallelGC:默认的是使用ParallelGC
CMS(ConcurrentMarkSweep):并发标记清除
程序执行时,并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停。
主动式中断:轮询标志的地方和安全点是重合的。
初始标记的时候,会Stoptheworld,此时没有用户线程执行。
并发标记的时候,可以与用户线程同时执行。
重新标记的时候,会Stoptheworld,此时也没有用户线程执行。
并发清除的时候,可以与用户线程同时执行。
停顿,停顿的是用户线程停顿的少。

出现Concurrent即代表GC线程可以和用户线程同步执行的阶段。
将标记拆分成了前5个阶段。
物理内存中的新生代与老年代被消除。
QPS(TPS):每秒钟request/事务数量
并发数:系统同时处理的request/事务数
G1有很好的吞吐量和响应能力。
G1可以回收部分老年代,CMS要回收全部的老年代。
哇,竟然都学过了。图中的内容也都知道,但是刚拿到这个图的时候不敢去看他。
executionengine:执行引擎
JITcompiler:即时编译器。
internal:内部的native:本地的
整个堆空间,不做块的区分。就当成是一个堆空间。每一个空间的使用情况都可能是三种情况之一。
对每种分代内存的大小,可以动态变化。
这些差异,源于G1对物理模型设计的变化。
G1:垃圾优先。即首先收集垃圾最多的分区。
新生代满的时候,G1对整个新生代进行回收。
CSet:一组将要被回收的空间的集合。
RSet存在的原理及价值。如上图所示,记录了Region中的引用关系。使得垃圾收集器不需要扫描整个堆找谁引用了当前分区中的对象,只需要扫描RSet即可。
Points-into:代表谁指向了我;
Point-out:我指向了谁
G1是将来OracleHotSpot的未来。
又开始卡了,图片放的太多了。哈哈哈哈。快结束了,坚持住。
globalconcurrentmarking全局并行标记
G1只有YoungGC和MixedGC
G1是不提供FullGC的
并发阶段,指的是全局并发标记的阶段
混合模式,指的是MixedGC
什么时候发生MixedGC
由一些参数控制,另外也控制着哪些老年代Region会被选入CSet当中
垃圾占比已经超过此参数设定的值
上图描述了RSet的记录情况,存放各个引用关系。
RSet其实是一个HashTabella,key是引用它的起始地址,value是一个集合,元素是cardtable的index。
混合GC,不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。
在进行并发标记时出现的标记错误的情况。而出来的算法
被标记,意思是可达的对象。被标记的对象都不应该被回收掉,都不是垃圾。
没有被标记的才是垃圾。
此时。C也不应该被垃圾回收。
灰色移除的目标标记为灰色。黑色引用新产生的对象标记为黑色
误标没什么关系,但是漏标的结果是致命的,影响线程的正确性。
针对于是上一个问题的解决方案
每天写业务代码,怎么能和别人拉开距离。
确定了一个待研究的主题,对这个主题进行全方面的剖析。高注意力的研究一个主题。
在Java当中,一个全局变量会被初始化几次。(问的问题不对。Java当中承担全局变量的是静态变量)>静态变量只会被初始化一次,在类加载器初始化的时候,静态变量会被首先初始化。
Java当中的泛型是怎么实现的。
强引用,软引用,弱引用,虚引用等概念。
谁都会忘,忘了去复习就好了。自己复习自己的笔记,能够瞬间勾起你的回忆。
学习,为什么不可以去看官方文档学习呢?看不懂英文的吗?
要知道,老师们讲的顺序都是按照官网的顺序来的。
总结,复习。有来有回。复盘。
2020年02月21日07:57:58整理笔记结束。原来这里边只能有一个一级标题。重新整理了两遍。哈哈哈