JVM简单介绍

2021/5/10 18:25:21

本文主要是介绍JVM简单介绍,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JVM体系

JVM

类加载器

作用:

  • 加载class文件进入JVM
  • 审查每个类应该由谁加载,采用双亲委托机制
  • 将class字节码重新解析成JVM要求的对象格式

ClassLoader的双亲委派加载机制

双亲委派加载机制即如果一类的加载器收到一个加载请求,它首先会将这个请求委托给父类加载器去加载,如果父类判断没有加载过这个类,在继续向其上一层加载器委派,每一层次的类加载器都是如此,则所有的类加载请求最终都会到达顶层的类加载器,只有父类加载器无法加载这个请求,子类才会去加载此类,这样可以避免重复加载同时防止用户自定义的类加载器替代java核心加载器。

类加载器

沙箱安全机制

什么是沙箱

沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离防止对本地系统造成破坏

最新的安全机制

当前最新的安全机制实现,则引入了域 (Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示 最新的安全模型(jdk 1.6)

安全模型

java沙箱的基本组件

  • 字节码校验器
  • 类装载器

Native、方法区

  • 本地方法栈

指的是被native修饰的方法,即非Java代码。

下图中getClass()没有方法体,是由Java的底层的C/C++来实现的。

例子

  • 方法区

保存的是类信息、常量、静态变量、JIT(即时)编译时代码。

栈是一种先进后出的数据结构,最后入栈的元素,最先读取出来。就像向箱子里放书一样,最后放进去的那本书,我们可以最先从箱子里取出来。

实例化栈的变化过程

public class People{
    String name; // 定义一个成员变量 name
    int age; // 成员变量 age
    Double height; // 成员变量 height
    void sing(){
        System.out.println("人的姓名:"+name);
        System.out.println("人的年龄:"+age);
        System.out.println("人的身高:"+height);
    }
    
    public static void main(String[] args) {
        String name; // 定义一个局部变量 name
    	int age; // 局部变量 age
    	Double height; // 局部变量 height
        
        People people = new People() ; //实例化对象people
        people.name = "张三" ;       //赋值
        people.age = 18;             //赋值
        people.stuid = 180.0 ;   //赋值
        people.sing();              //调用方法sing
    }
}

代码解析:

这段代码首先定义三个成员变量:String name、int age、Double height 这三个变量都是只声明了没有初始化,然后定义了一个成员方法 sing();

在 main()方法里同样定义了三个一样的变量,只不过这些是局部变量;

在main() 函数里实例化对象 people , 内存中在堆区内会给实例化对象 people 分配一片地址,紧接着我们对实例化对象 people 进行了赋值。people 调用成员方法 sing() 。mian()函数打印输入人的姓名,人的年龄和人的身高,系统执行完毕。

下面通过图解法展示实例化对象的过程中内存的变化:

图1

在程序的执行过程中,首先类中的成员变量和方法体会进入到方法区,如图:

图2

程序执行到 main() 方法时,main()函数方法体会进入栈区,这一过程叫做进栈(压栈),定义了一个用于指向 Person 实例的变量 person。如图:

图3

程序执行到 Person person = new Person(); 就会在堆内存开辟一块内存区间,用于存放 Person 实例对象,然后将成员变量和成员方法放在 new 实例中都是取成员变量&成员方法的地址值 如图:

图4

接下来对 person 对象进行赋值, person.name = “小二” ; perison.age = 13; person.height= 180.0;

先在栈区找到 person,然后根据地址值找到 new Person() 进行赋值操作。如图:

图5

当程序走到 sing() 方法时,先到栈区找到 person这个引用变量,然后根据该地址值在堆内存中找到 new Person() 进行方法调用。

在方法体void sing()被调用完成后,就会立刻马上从栈内弹出(出站 )

最后,在main()函数完成后,main()函数也会出栈 如图:
图6

三种JVM

  • Sun公司 --- HotSpot(TM)
  • BEA公司 --- JRockit
  • IBM公司 --- J9 VM

heap,一个JVM只有一个堆内存,堆的内存大小是可以调节的。

堆内存中分为3个区域:

  • 新生区
    • 伊甸园区-----Eden space
    • 幸存0区
    • 幸存1区
  • 养老区
  • 永久区-----JDK8以后变为:元空间

GC垃圾回收,主要是在伊甸园区和养老区

OOM

当堆内存不足时,报错即为:OOM(java.lang.OutOfMemoryError.java heap space)

使用JPofiler工具分析OOM原因

MAT、JPofiler作用:

  • 分享Dump内存文件,快速定位内存泄露
  • 获得堆中的数据,获得大的对象
  • 。。。。。。

IDEA安装控件:

setting-Plugins(搜索jpofiler)-没有到商店搜索-search in marketplace

JPofiler下载:

JPofiler下载地址

VM运行参数:

打印栈溢出,OOM信息

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

-Xms:设置初始化内存分配大小

-Xmx:设置最大分配内存

打印GC垃圾回收信息:

-Xms1m -Xmx8m -XX:+PrintGCDetails

新生区

  • 类:诞生和成长的地方,甚至死亡
  • 伊甸园,所有对象都在这里new出来的。
  • 幸存者区

老年区

储存GC没有回收掉的

永久区

这个区域常驻内存的,用来存放JDK自身携带的Class对象,interface元数组,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭虚拟机时才会释放这个区域的内存

GC垃圾回收

GC的作用区域:堆、方法区

GC分为2种:轻GC(作用与新生区和幸存区)、重GC(作用域全局)

注:当一个对象经历了15次(默认值)GC后,还没有死,就会进入养老区(老年区)

设置进入老年代的时间:-XX:MaxTenuringThreshold=5

GC算法

引用计数法

思想:每一个对象都有一个counter,只要有任何一个对象引用了该对象,则其counter加1,当引用失效时,counter减1,当counter为0时,对象不存在任何引用,在GC时被清除

GC过程:counter在每次引用生效和失效时进行加减法操作,并判断是否为0,是则清除

优点:思想和实现都很简单(只需要为每一个对象配备一个整型的计数器)

缺点:1、无法处理循环引用问题,可能会造成死锁:比如对象A和对象B互相引用,但是不存在其他对象引用A和B,此时A和B属于不可达的对象,即垃圾对象,但是counter无法识别此类垃圾对象间的互相引用,从而引起内存泄露,不能GC

2、counter要求在每次引用生效和失效时进行加减法操作,在一定程度上影响系统性能

复制算法

思想:将内存空间分为两块相同的存储空间,每次只使用一块,GC时,将正在使用的内存中的存活对象复制到另一块存储空间中,然后清除正在使用的空间的所有对象

GC过程:先复制,再清除

优点:存活对象相对少时,效率很高(因为需要复制的对象少),存活对象复制到另一空间时,解决了空间碎片问题

缺点:系统内存只能使用一半的内存空间,浪费了内存空间,而且如果存活对象相对多的话,比较耗时

注意:复制算法比较适用于新生代。因为在新生代中,垃圾对象通常会多于存活对象

标记清除算法

思想:标记所有的可达对象(存在引用的对象),则未被标记的对象就是不存在引用的垃圾对象,GC时清除所有未被标记的对象

GC过程 标记清除法的GC时经历标记 + 清除两个过程,先标记,后清除、

优点:只存在循环引用不存在其他引用的对象不会被标记,解决了循环引用问题

缺点:可能会产生空间碎片(不连续的内存空间),不连续的内存空间在内存分配时的工作效率低于连续的内存空间,尤其是对大对象的的内存分配

标记压缩清楚算法

思想:标记压缩法是对标记清除法的优化,所以也叫标记清除压缩法。和标记清除法一样,先标记所有的可达对象(存在引用的对象),不同的是,标记完成后并不是直接清除未标记的垃圾对象,而是将所有的被标记的对象(即存活对象)压缩到内存空间的一端后在清理边界外所有的空间。

GC过程:分为标记+压缩 + 清除三个步骤

优点:解决了标记清除法带来的空间碎片问题,又不需要折损可使用空间(复制算法折损了可使用空间)

分代算法(分代收集算法,主流)

思想:将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法,以提高垃圾回收的效率。

GC过程:因为不同的对象使用的算法不一致,所以GC的过程也不一样,例如,新生代使用复制算法,老年代使用标记压缩法

优点:提高了垃圾回收的效率

应用场景:分代的思想被现有的虚拟机广泛使用,几乎所有的垃圾回收器都区分新生代和老年代。

卡表:

概念及其意义:对于新生代和老年代来说,通常新生代回收的频率很高,但是每次回收的时间都很短,而老年代回收的频率比较低,但是被消耗很多的时间。为了支持高频率的新生代回收,虚拟机可能使用一种叫做卡表的数据结构,卡表为一个比特位集合,每一个比特位可以用来表示老年代的某一区域中的所有对象是否持有新生代对象的引用,这样一来,新生代GC时,可以不用花大量时间扫描所有老年代对象,来确定每一个对象的引用关系,而可以先扫描卡表,只有当卡表的标记为1时,才需要扫描给定区域的老年代对象,而卡表为0的所在区域的老年代对象,一定不含有新生代对象的引用。

JMM

我们常说的JVM内存模式指的是JVM的内存分区;而Java内存模式是一种虚拟机规范

详情见:Java内存模型(JMM)总结



这篇关于JVM简单介绍的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程