publicclassJava3y{//姓名privateStringname;//年龄privateintage;//.....各种get/set方法/toString}一个测试类:
publicclassJava3yTest{publicstaticvoidmain(String[]args){Java3yjava3y=newJava3y();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默认有三种类加载器:
各个加载器的工作责任:
工作过程:
其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
好处:
特别说明:
加载器加载到jvm中,接下来其实又分了好几个步骤:
一般我们可能会想:JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行-->解析器解析。
但如果是这样的话,那就太慢了!
我们的JVM是这样实现的:
热点代码解释:一、多次调用的方法。二、多次执行的循环体
使用热点探测来检测是否为热点代码,热点探测有两种方式:
目前HotSpot使用的是计数器的方式,它为每个方法准备了两类计数器:
按我们程序来走,我们的Java3yTest.class文件会被AppClassLoader加载器(因为ExtClassLoader和BootStrap加载器都不会加载它[双亲委派模型])加载到JVM中。
随后发现了要使用Java3y这个类,我们的Java3y.class文件会被AppClassLoader加载器(因为ExtClassLoader和BootStrap加载器都不会加载它[双亲委派模型])加载到JVM中
详情参考:
扩展阅读:
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
首先我们来了解一下JVM的内存模型的怎么样的:
简单看了一下内存模型,简单看看每个区域究竟存储的是什么(干的是什么):
我来宏观简述一下我们的例子中的工作流程:
从微观上其实还做了很多东西,正如上面所说的类加载过程(加载-->连接(验证,准备,解析)-->初始化),在类加载完之后jvm为其分配内存(分配内存中也做了非常多的事)。由于这些步骤并不是一步一步往下走,会有很多的“混沌bootstrap”的过程,所以很难描述清楚。
参考资料:
嗯,然后就懵逼了。我摘抄一下他的例子:
publicstaticvoidmain(String[]args){Strings=newString("1");s.intern();Strings2="1";System.out.println(s==s2);Strings3=newString("1")+newString("1");s3.intern();Strings4="11";System.out.println(s3==s4);}打印结果是
调换一下位置后:
publicstaticvoidmain(String[]args){Strings=newString("1");Strings2="1";s.intern();System.out.println(s==s2);Strings3=newString("1")+newString("1");Strings4="11";s3.intern();System.out.println(s3==s4);}打印结果为:
看完了之后,就更加懵逼了。
后来,在zhihu上看到了这个回答:
结合网上资料和自己的思考,下面整理一下对常量池的理解~~
针对于jdk1.7之后:
常量池存储的是:
现在我们的运行时常量池只是换了一个位置(原本来方法区,现在在堆中),但可以明确的是:类加载后,常量池中的数据会在运行时常量池中存放!
HotSpotVM里,记录internedstring的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容
字符串常量池只存储引用,不存储内容!
再来看一下我们的intern方法:
*Whentheinternmethodisinvoked,ifthepoolalreadycontainsa*stringequaltothis{@codeString}objectasdeterminedby*the{@link#equals(Object)}method,thenthestringfromthepoolis*returned.Otherwise,this{@codeString}objectisaddedtothe*poolandareferencetothis{@codeString}objectisreturned.1.6.2解析题目本来打算写注释的方式来解释的,但好像挺难说清楚的。我还是画图吧...
第二句:s.intern();发现字符串常量池中已经存在"1"字符串对象,直接返回字符串常量池中对堆的引用(但没有接收)-->此时s引用还是指向着堆中的对象
第三句:Strings2="1";发现字符串常量池已经保存了该对象的引用了,直接返回字符串常量池对堆中字符串的引用
很容易看到,两条引用是不一样的!所以返回false。
第二句:s3.intern();发现"11"对象并没有在字符串常量池中,于是将"11"对象在字符串常量池中保存当前字符串的引用,并返回当前字符串的引用(但没有接收)
第三句:Strings4="11";发现字符串常量池已经存在引用了,直接返回(拿到的也是与s3相同指向的引用)
根据上述所说的:最后会返回true~~~
如果还是不太清楚的同学,可以试着接收一下intern()方法的返回值,再看看上述的图,应该就可以理解了。
下面的就由各位来做做,看是不是掌握了:
publicstaticvoidmain(String[]args){Strings=newString("1");Strings2="1";s.intern();System.out.println(s==s2);//falseStrings3=newString("1")+newString("1");Strings4="11";s3.intern();System.out.println(s3==s4);//false}还有:
在C++中,我们知道创建出的对象是需要手动去delete掉的。我们Java程序运行在JVM中,JVM可以帮我们“自动”回收不需要的对象,对我们来说是十分方便的。
虽然说“自动”回收了我们不需要的对象,但如果我们想变强,就要变秃..不对,就要去了解一下它究竟是怎么干的,理论的知识有哪些。
首先,JVM回收的是垃圾,垃圾就是我们程序中已经是不需要的了。垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。判断哪些对象“死去”常用有两种方式:
现在已经可以判断哪些对象已经“死去”了,我们现在要对这些“死去”的对象进行回收,回收也有好几种算法:
(这些算法详情可看下面的面试题内容)~
上面所讲的垃圾收集算法只能算是方法论,落地实现的是垃圾收集器:
上面这些收集器大部分是可以互相组合使用的
很多做过JavaWeb项目(ssh/ssm)这样的同学可能都会遇到过OutOfMemory这样的错误。一般解决起来也很方便,在启动的时候加个参数就行了。
上面也说了很多关于JVM的东西--->JVM对内存的划分啊,JVM各种的垃圾收集器啊。
内存的分配的大小啊,使用哪个收集器啊,这些都可以由我们根据需求,现实情况来指定的,这里就不详细说了,等真正用到的时候才回来填坑吧~~~~
拿些常见的JVM面试题来做做,加深一下理解和查缺补漏:
根据JVM规范,JVM内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
具体可能会聊聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)
内存泄漏的原因很简单:
常见的内存泄漏例子:
publicstaticvoidmain(String[]args){Setset=newHashSet();for(inti=0;i<10;i++){Objectobject=newObject();set.add(object);//设置为空,这对象我不再用了object=null;}//但是set集合中还维护这obj的引用,gc不会回收object对象System.out.println(set);}解决这个内存泄漏问题也很简单,将set设置为null,那就可以避免上诉内存泄漏问题了。其他内存泄漏得一步一步分析了。
内存泄漏参考资料:
内存溢出的原因:
解决:
这里的线程栈应该指的是虚拟机栈吧...
JVM规范让每个Java线程拥有自己的独立的JVM栈,也就是Java方法的调用栈。
当方法调用的时候,会生成一个栈帧。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息
线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素。
通过jstack工具查看线程状态
这题就依据fullGC的触发条件来做:
双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。
Java的类加载是否一定遵循双亲委托模型?
检验一下是不是真懂了:
classDerviedextendsBase{privateStringname="Java3y";publicDervied(){tellName();printName();}publicvoidtellName(){System.out.println("Derviedtellname:"+name);}publicvoidprintName(){System.out.println("Derviedprintname:"+name);}publicstaticvoidmain(String[]args){newDervied();}}classBase{privateStringname="公众号";publicBase(){tellName();printName();}publicvoidtellName(){System.out.println("Basetellname:"+name);}publicvoidprintName(){System.out.println("Baseprintname:"+name);}}输出数据:
当younggen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).
YGC和FGC是什么
什么时候执行YGC和FGC
GC最基础的算法有三种:
具体:
stackoverflow错误主要出现:
permgenspace错误(针对jdk之前1.7版本):
总的来说,JVM在初级的层面上还是偏理论多,可能要做具体的东西才会有更深的体会。这篇主要是入个门吧~