jvm全称是JVM(Java Virtual Machine,Java虚拟机),JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。
JVM的作用:为java程序提供一个可以运行的环境;Java程序的跨平台特性主要就是因为JVM实现的。在编译java程序时会将写好的源程序通过编译器编译生成.class文件(又称为字节码文件),之后就是通过JVM内部的解释器将字节码文件解释成为具体平台上的机器指令执行,所以就可以实现java程序的跨平台特性。
JVM内部体系结构大致分为三部分:类装载器(ClassLoader)子系统,运行时数据区和执行引擎。
如下图所示:
作用:加载 .class文件;类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。
JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:
它用来加载 Java 的核心类,是用**原生代码**来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由 **C++** 实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包中的类。==由Java语言实现,父类加载器为null==。
被称为系统(也称为应用APP)类加载器,由Java语言实现,**父类加载器为ExtClassLoader。** 它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。==如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器==。
![在这里插入图片描述](https://img-blog.csdnimg.cn/529b272956f8434b88f7083976bad9a5.png)
双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。
(1)双亲委派机制作用:
(2)执行顺序为:应用程序加载器=>扩展类加载器=>根加载器(最终执行);根加载器检查是否能够加载当前类,如果可以加载,就结束,不能就会抛出异常,通知子加载器进行加载。如果到了应用程序加载器还没找到,就会报错(Class Not Found);
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。如下图所示 JDK1.0安全模型:
但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示 JDK1.1安全模型:
在 Java1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示 JDK1.2安全模型:
当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示 最新的安全模型(jdk 1.6)
内存空间小,线程私有。指向方法区中的方法字节码(用来存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令;字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。
注意:如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
属于所有线程所共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。简单来说,所有定义的方法的信息都保存在该区域。比如构造函数,接口代码;
注意:运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
Heap:一个JVM只有一个堆内存,堆内存的大小是可以调节的;线程共享,主要是存放对象实例和数组。类加载器读取了类文件后,一般会把类的常量、方法、变量放到堆中,保存我们所有引用类型的真实对象。
堆内存中还要细分为以下三个区域:
程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。
注意:
GC触发时间
OutOfMemoryError(OOM)解决措施:
-Xms 设置初始化内存分配大小 1/64
-Xmx 设置最大分配内存,默认1/4
-XX:+PrintGCDetails 打印GC垃圾回收信息
需要配置命令:-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError //OOM dump
查找OOM错误可以在VM中设置上述最后一条指令,并且通过JPofiler工具来分析OOM发生的原因。
从根集合出发,将能访问到的对象打上标记,然后遍历堆,将没有标记的对象清除。
优点:不会挪动对象在堆中的位置
缺点:容易产生内存碎片
把堆划分为两部分,每次分配对象的时候只使用其中一半.当标记过程结束后,把存活的对象全都挪到堆的另一半中.
优点:不会产生内存碎片
缺点:浪费了内存空间
优点:不会产生内存碎片
缺点:比标记清除算法多了一次扫描时间。多了一个移动成本。
根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。
新生代:每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。
老年代:老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用标记 —— 清除或者标记 —— 整理算法回收。
垃圾收集器就是内存回收的具体实现。**
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡
哪些对象可以作为GC Roots呢?
对象可以被回收,就代表一定会被回收嘛?
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
类需要同时满足下面 3 个条件才能算是 “无用的类” :