JVM-java虚拟机
经常听到描述java的一句话是,“一处编译,处处运行”。
不同系统有不同系统的编译方式,它们将你写的命令经过自己的方式翻译成二进制,让计算机明白你想要让他做的事。但由于每种系统编译的方式不一致,导致你可能在这个系统写好的命令,放在另外一个系统就认不出来了。
这时候java就说,别这么麻烦,你们这些系统只需要给我一个空间,我直接帮你给计算机说。
首先,java程序能在任何系统上运行,当然需要系统能为它开一个属于虚拟机的空间。这听起来就像执行一个程序,而实际上一个java程序就对应了一个虚拟机。
内存分布
既然已经为虚拟机分配了一块属于它的地方,那它又是如何利用这块地方的呢?
在其中,虚拟机又为自己划分出来五个区域。
- 线程计数器
在一个java程序中,可能存在多个线程同时执行的情况。我们知道,说的是多线程同时执行,其实就单核而言这几个线程还是同步执行的,每个线程都是在抢占资源的时间,利用这抢过来的一点点时间做自己线程该做的事。而由于这个时间极具短暂,导致看上去线程就像在异步一起执行一样。当然,这只限于单核情况下。
但是,这就存在了一个问题,当这些线程如此频繁的交替工作时,当一个线程好不容易抢到资源时。它是怎么知道自己已经进行到哪一步了呢?如果不记下自己执行到的地方,难道每次都重新开始吗?那么,线程可能很难完成自己的任务。
而线程计数器就是记录了每个线程执行的代码位置,方便等该线程抢占到资源时,继续执行自己的命令。
- java虚拟栈
这一块主要保存了存储局部变量表,操作数栈,动态链接,方法出入口等。
所谓的局部局部变量表就是保存的局部变量、基本数据、引用地址、返回值地址等。
其实总的来说主要就是存储变量和这些变量相关信息,以及基础数据。
- 本地方法栈
顾名思义,就是存储的本地方法,执行本地方法的地方。
什么是本地方法?
就是java虚拟机需要使用的系统环境相关方法。就比如,一个程序在系统中运行,都需要系统执行语句将这个程序调用起来。同理,java也需要使用系统本地的语句去执行一些命令,就如java的核心API,当连一些基础的东西都没有的时候,自然需要先从系统环境运行这些命令。
就像是我有一个大宝箱,里面放了关于java的所有内容,但要打开这个宝箱,仍需要从外部得到一把打开它的钥匙。
一般的java本地方法,变量等都会用native关键词去修饰。
- java堆
java堆这块应该是java虚拟机中占据最大空间的内存块了,它主要放着的就是我们的实例,java编程最最常见的东西。
- 方法区
这一块存的主要是虚拟机需要加载的东西,如常量、静态变量及类的相关信息等。其实就是存储的一些不怎么会变动且在整个虚拟机内可能共享的数据。
- 访问方式
那么,既然变量与实例存放的是在不同区域,它们又是如何绑定到一起的呢?
这一点就要涉及到对象访问方式上,一种方法是在堆中单独拿出一块地方存放对象的实例数据和对象类型的数据的地址,这种被叫做句柄访问。
另外一种则是在栈中直接指向对象类型指针和对象的实例数据,这种是直接访问。
看上去区别好像不大,实际上最主要的区别是前一个相当于建立了一个中间层,访问时先找的的是这个中间层再得到了实例数据和类型地址。而另一个则是直接跳过这个中间层,直接得到实例数据和类型地址。后者的速度要快于前者。
垃圾回收
垃圾回收机制可以说是java的一大特点,也可能是java的一大弊端。
垃圾回收做什么的?
说白了就是把不用的对象清理掉,留出空间。而这个过程java则将其自动实现,平时你会很难发现自己使用过的无用空间是否被释放,更不用去关注这些。而这自然也极大的减少了我们命令设计者的工作内容。
那么,JVM到底如何实现自动垃圾回收呢?
首先,需要知道,到底什么样的对象应该被回收掉。计算机不像设计者,可以根据业务逻辑等方式判断这个对象应该在什么时候被回收,于是java的设计人员设计了两种方式去判断这个对象到底是否该被回收。
- 引用计数法
- 可达性计分析算法
分代收集
但就算知道了哪些对象该被清理也没有具体执行,现在只完成了发现还没有完成清除这步。
实际上每当一个对象被创建时都会被放进一个叫Eden的区域,当这个区域的空间占用率到达一定程度时,GC就开始运转了。它扫描这个空间的所有对象,根据上面的方法判断该对象是否该被清除。大多数对象将在这一过程中消亡,而存活下来的对象则会被存放到另一个名叫Survivor的区域,这个区域分成两块。其中一块区域存放对象,而另一块则作为下次存放对象的地方。
而当新的对象放入正在存放对象的Survivor1中时,到达某个时间点,JVM发现这个Survivor1也快不够用,于是新的一轮GC再次开始,它会扫面Survivor1中的所有对象,并将不会被清除的对象复制到Survivor2中,等扫描完成时,就将Survivor1清空。如此返回清理Survivor中的对象。
而在上面的过程中,JVM还会给每一个在GC中存活下来的对象计数,当次数达到一定程度时,这个对象就会被送进一个叫老年代的区域。这个区域的对象虽然挺过了重重考验,但是当某天老年代的空间占用率也达到一定程度时,一次大范围对所有对象扫面的 FULL GC就会开始,它会逐个扫描从最开始的Eden到老年代的所有对象。
Java源码编译机制
Java源文件(.java) -- Java编译器 --> Java字节码文件 (.class) -- Java解释器 --> 执行
字节码就是二进制文件
类加载机制
java类的加载首先说到的就是类的加载器
- Bootstrap ClassLoader:根类加载器
该加载器主要加载java核心API,为jre/lib/rt.jar包中的所有类,而这其中就包含了java的定义和规范。因为该加载器是加载java类的大门,所以是由其他语言实现。
- Extension ClassLoader:扩展类加载器
加载Java扩展API(lib/ext中的类)
- App ClassLoader:应用加载起
加载Classpath目录下定义的class
- Custom ClassLoader:自定义加载器
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据J2EE规范自行实现ClassLoader
顺带一提类的加载机制是双清委派机制