浅谈JVM

JVM

JVM位置

浅谈JVM_第1张图片

JVM结构

浅谈JVM_第2张图片

类加载器

作用:加载Class文件

浅谈JVM_第3张图片

类是一个模板,对象是一个具体的

1.虚拟机自带的加载器

2.启动类(根)加载器 rt.jar

3.扩展类加载器 ext.jar

4.应用程序(系统类)加载器

加载器顺序

App—EXT—BOOT

先去BOOT里找,没有去EXT里找,最后到App里找

双亲委派机制

1.类加载器收到类加载的请求

2.将这个请求向上委派给父类加载器去完成,一直向上委托,直到启动类加载器

3.启动加载器检查是否能够加载当前这个类,能够加载就结束,使用当前这个加载器,否则抛出异常,通知子加载器进行加载

4.重复步骤3

报错产生:ClassNotFoundException

为什么要设计这种机制

这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。

Native

native:凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层C语言的库

凡是用native修饰的方法,会进入本地方法栈

调用本地方法本地接口 JNI(Java Native Interface)

JNI作用:扩展Java的使用,融合不同语言为Java所用!最初是为了融合C、C++

JVM在内存中专门开辟了一个区域 本地方法栈(Native Method Stack)来登记Native 方法

最终执行的时候,加载本地方法库中的方法通过JNI调用

Native作用:一般用用于操作硬件 eg:Java驱动打印机,系统管理等,企业级开发很少使用

程序计数器(PC寄存器)

程序计数器 Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码,是一个非常小的内存空间,几乎可以忽略不计

方法区

Method Area

方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法 例如构造函数,接口代码也在此定义

所有定义的方法信息都保存在该区域,此区域属于共享区间

存放

静态变量、常量、类信息(构造方法、接口定义)、运行时常量池,实例变量存在堆内存中和方法区无关

Ps: static、final、Class、常量池(类里面的常量信息)

栈内存

主管程序运行,生命周期和线程同步

线程结束,栈内存释放,不存在垃圾回收问题

一旦线程结束,栈就Over

栈存放

8大进本类型+对象引用+实例方法

程序正在执行的方法一定在栈的顶部

栈满了:StackOverflowError

栈+堆+方法区交互关系

浅谈JVM_第4张图片

三种JVM

  • Sun公司 HotSpot
  • BEA JRockit 号称世界上最快的JVM
  • IBM J9 VM

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

大致分为三类

  • 新生区(伊甸园) young/new
  • 养老区 old
  • 永久区

浅谈JVM_第5张图片

GC回收主要是在伊地园区和养老区

假设内存满了,就会报OOM,堆内存不够

JDK8以后,永久存储区改了个名字 叫元空间

新生区

  • 类:诞生和成长的地方,甚至死亡;
  • 伊甸园区:所有对象都在伊甸园区new出来的;
  • 幸存区(0,1)

老年区

浅谈JVM_第6张图片

经研究:99%的对象都是零时对象

永久区

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

一个启动类加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类,不断被加载。直到内存满,就会出现OOM

  • JDK1.6之前:永久代,常量池是在方法区
  • JDK1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
  • JDK1.8之后:无永久代,常量池在元空间

堆模型:

浅谈JVM_第7张图片

元空间:逻辑上存在,物理上不存在

默认情况下:分配的总内存是电脑内存的1/4,初始化内存为1/64

解决OOM

  • 尝试扩大堆内存看结果
  • 分析内存,看一下那个地方出现了问题(专业工具)

在一个项目中,突然出现了OOM故障,那么该如何排除?研究为什么出错~

  • 能够看到代码第几行出错:内存分析工具,MAT,Jprofiler
  • Debug,一行行的分析代码

MAT,Jprofiler作用

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

常用参数

-Xms 设置初始化内存分配大小 默认1/64

-Xmx 设置最大内存分配大小 默认1/4

-XX:+PrintGCDetails 打印GC垃圾回收信息

-XX:+HeapDumpOnOutOfMemoryError //OOM Dump

GC:垃圾回收

浅谈JVM_第8张图片

JVM在进行GC时,并不是对这三个区域统一回收,大部分时候回收会在新生代~

  • 新生代
  • 幸存区(from , to)
  • 老年代

GC两种:轻GC(普通的GC),重GC(全局GC)

GC算法

引用计数法

复制算法

主要作用于年轻代

每次GC都会将Eden活的对象移到幸存区中,一旦Eden区被GC后,就会是空的

幸存区(from,to)而言,谁是空的谁是to

当一个对象经历了15(默认)次GC,都还没有死,-XX:MaxTenuringThreshold=15,这个参数可以设定进入老年代的时间

好处:没有内存碎片

坏处:浪费了内存空间,有一半空间永远是空的 (to区)

最佳使用场景:对象存活度较低的时候;新生区

标记清除算法

1.扫描,扫描内存中的对象,对活着的对象进行标记

2.清除,对没有标记的对象进行清除

优点:不需要额外的空间

缺点:两次扫面(标记扫描,清除扫描),严重浪费时间,会产生内存碎片

标记压缩清除算法

1.扫描,扫描内存中的对象,对活着的对象进行标记

2.压缩,将标记的对象向一端移动

3.清除,对没有标记的对象进行清除

优点:

缺点:

总结:

内存效率:复制算法>标记清除算法>标记压缩清除算法(时间复杂度)

内存整齐度:复制算法=标记清除算法>标记压缩清除算法

内存利用率:标记清除算法=标记压缩清除算法>复制算法

年轻代:

  • 存活率低
  • 复制算法

老年代:

  • 区域大,存活率低
  • 标记清除算法(内存碎片不是太多)+标记压缩混合实现

你可能感兴趣的:(java,jvm,java)