JVM
一、JVM的位置
二、JVM的体系结构
三、类加载器及双亲委派机制
1. 类加载器的作用
负责将.class文件给加载到JVM中去执行。
2. 类加载器分类
(1)Bootstrap classLoader
:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
(2)ExtClassLoader
:主要负责加载jre/lib/ext目录下的一些扩展的jar。
(3)AppClassLoader
:主要负责加载应用程序的主函数类
3. 双亲委派机制
当一个.class文件需要被加载时,首先会在AppClassLoader中检查是否加载过,如果被加载过那就无需再加载,如果没有被加载过,会接着拿到父加载器ExtClassLoader,然后检查是否加载过,如果被加载过那就无需再加载,如果没有被加载过,会接着拿到父加载器Bootstrap classLoader进行检查是否被加载过。到此,开始从Bootstrap classLoader进行判断是否能加载,如果能加载,则加载,不能则让子类加载器ExtClassLoader、AppClassLoader依次判断去加载,如果一直到最后都无法加载,则会抛出异常ClassNotFoundException
4. 双亲委派机制的优点
防止恶意替换系统级别的类,如下图所示,想替换java.lang.String失败
四、沙箱安全机制
(1)限制应用程序对系统资源的访问的运行环境
(2)沙箱提供的环境相对于每一个运行的程序都是独立的,而且不会对现有的系统产生影响。
五、Native 关键字
1. Native说明
凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层C语言的库。
例如线程中的start0
2. Native调用方式
首先进去本地方法栈中,通过java本地接口(JNI
,java native interface
),调用本地方法库中的方法。其中本地方法栈专门用来登记native方法。
3. JNI的作用
扩展java的使用,融合不同的编程语言为java所用。此外还可以连接硬件,如驱动打印机,管理系统等。
六、PC寄存器
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
七、方法区
- 方法区是被所有线程共享。
- 所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
- 静态变量static、常量final、类信息Class(构造方法、接口定义)、运行时的常量池,他们都存在方法区中,但是实例变量存在堆内存中,和方法区无关。
八、java类在内存实例化的过程
-
类实例化首先加载的是静态属性和变量、静态代码块、代码块、最后是构造函数
-
过程
(1)加载类的信息,类中的成员变量、方法体加载到方法区;
(2)程序进入main方法,main函数压栈(进入栈区)同时定义了一个变量person,该变量将来指向一个Person实例对象;
(3)在堆内存中开辟—块空间,存放new来的对象,并将其在方法区对应的成员变量、成员方法(地址值)拷贝过来,然后让栈中的变量person指向该对象示例
(4)对new出来的person对象赋值,先在栈区找到person变量,然后根据地址值在堆中找到实例对象进行赋值操作。
(5)当程序走到lego方法时,先到栈区找到person这个变量,然后根据该地址值在堆内存中找到实例对象,再进行方法调用。将lego方法压入栈,被调用完成后,就会立刻马上从栈内弹出,最后,在main函数完成后,main函数也会出栈。
九、堆
- Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
- 类加载器读取了类文件后,一般会把类,方法,常量,变量放入堆中,此外,堆还保存我们所有引用类型的实例对象;
- 堆内存中还要细分为三个区域:
(1)新生区:包括伊甸园区,幸存0区,幸存1区
(2)养老区
(3)永久区:又名元空间 - 垃圾回收一般在伊甸园区,养老区
- 假设内存满了,出现OOM,堆内存不够! java.lang.OutOfMemoryError: Java heap space
OOM解决方法:
(1)尝试扩大堆内存看结果
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
(2)分析内存,看一下那个地方出现了问题(专业工具>
十、新生区、老年区、永久区
1. 新生区
(1)包括:伊甸园区、幸存1区、幸存2区
(2)新生区是类诞生和成长的地方,甚至死亡;
(3)所有的对象都是在伊甸园区new出来的
2. 老年区
在新生区进行轻量GC之后,若是新生区满了,在进入老年区,若是老年区满了,则会进行重量GC。
3. 永久区(元空间)
(0)元空间:逻辑上存在:物理上不存在
(1)这个区域常驻内存的。
(2)用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境或类信息
(3)这个区域不存在垃圾回收
(4)关闭VM虚拟就会释放这个区域的内存。
(5)一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载。直到内存满,就会出现OOM;
十一、OOM解决方法及其内存调优
java.lang.OutOfMemoryError: Java heap space
OOM解决方法:
(1)尝试扩大堆内存看结果
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
(2)用能够看到代码是第几行出错的工具:内存快照分析工具。如:MAT,Jprofiler插件
MAT,Jprofiler作用:分析Dump内存文件,快速定位内存泄露;获得堆中的数据;
十二、Jprofiler的使用
-
假设现在有一个会报出OOM的错误代码如下,且在写代码时不知道抛出的时异常还是错误,而我们一般写的时异常,所以无法捕获到Error
-
为了测试,手动配置堆大小VM options:
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
其中:
-Xms:设置初始化内存分配大小,默认为1/64
-Xmx:设置最大分配内存,默认 1/4
-XX:+PrintGCDetails 打印GC拉圾回收信息
-XX :+HeapDumpOnOutOfMemoryError
-
运行结果
-
找到该项目工程下的hprof文件,并且双击打开
-
首先看Biggest Objects,发现ArrayList占用了87%的堆,说明问题出在ArrayList上
-
其次接着看线程,确定问题出在哪一个线程,并且看到问题出在第21行
十三、GC
- GC一般在堆中进行
- GC分为轻量型和重量型
- GC大部分回收都在新生区里面的伊甸园区
十四、GC常用算法
1. 引用计数法
给每个堆中的对象设置一个计数器,每使用一次该对象,计数器加一,清楚计数为0的
2. 复制算法
3. 标记清除算法
部分资料参考B站狂神说Java