大家好,我是小轩,Java内存模型在面试过程中必不可少,最近也是整理了一些笔记。
Java的类加载过程
jvm将.class类文件信息加载到内存并解析成对应的class对象的过程,注意:jvm并不是一开始就把所有的类加载进内存中,只是在第一次遇到某个需要运行的类才会加载,并且只加载一次
主要分为三部分:
1、加载,2、链接(1.验证,2.准备,3.解析),3、初始化
一、加载
1、类加载器
上面已经提到了类加载的过程是由类加载来完成,Java内部提供了几种类加载器:
启动类加载器(Bootstrap Class Loader)
扩展类加载器(Extension Class Loader)
应用程序类加载器(Application Class Loader)
① 启动类加载器(Bootstrap Class Loader)
负责加载存放在JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等。
② 扩展类加载器(Extension Class Loader)
主要负责加载存在在JRE的lib/ext目录下的JAR包以及通过java.ext.dirs系统变量指定的路径中的JAR包。
③ 应用程序类加载器(Application Class Loader)
主要负责加载ClassPath路径下的所有JAR包。
开发人员还可以通过自定义类加载器来进行扩展。
2、双亲委派机制
Java中类加载器之间的关系通过双亲委派机制来保证
双亲委派机制确保在加载每个类时,由当前的类加载器委派给它的父(上层)类加载器加载,依次类推直到启动类加载器,如果启动类加载器在加载的路径下找不到目标类,则再交给子(下层)类加载器加载,同样依次类推,如果所有的父加载器都找不到目标类,则在当前类加载器的路径中查找并加载目标类。
简单来说就是父加载器优先加载,父加载器无法加载再由子加载器加载。
好处就是可以避免重复加载。
二、链接
验证:(验证class文件的字节流是否符合jvm规范)
准备:为类变量分配内存,并且进行赋初值
解析:将常量池里面的符号引用(变量名)替换成直接引用(内存地址)过程,在解析阶段,jvm会把所有的类名、方法名、字段名、这些符号引用替换成具体的内存地址或者偏移量。
三、初始化
主要对类变量进行初始化,执行类构造器的过程,换句话说,只对static修试的变量或者语句进行初始化。
范例:Person person = new Person();为例进行说明。
Java编程思想中的类的初始化过程主要有以下几点:
1. 找到class文件,将它加载到内存
2. 在堆内存中分配内存地址
3. 初始化
4. 将堆内存地址指给栈内存中的p变量
上面的过程发生在Java虚拟机中
Java虚拟机一句话说就是:Java 虚拟机就是我们java应用的运行环境,Java语言使用Java虚拟机屏蔽操作系统和机器之间的差异,达到Write Once Run Anywhere。
通常由javac 去编译程序源代码,转换成Java字节码,JVM通过解析字节码文件,将其翻译成对应的机器指令,逐条读入,逐条解析。
JVM整体结构
JVM称为Java虚拟机,它要对应用程序的线程等分配内存空间,还要负责加载类、解析执行编译后的字节码文件,所以整个JVM主要就包含了运行时数据区、类加载子系统和字节码执行引擎。
运行时数据区是在内存中的,所以这一部分也成为JVM的内存模型。这里要与JMM(Java内存模型)区分开。
内存模型主要包含五部分的内容:堆、栈、本地方法栈、方法区(元空间)、程序计数器。
堆:JVM管理的最大一块内存空间,它是所有线程所共享的一块区域。在虚拟机启动的时候创建,该区域的唯一目的就是为了存放对象实例。
栈(虚拟机栈):也可以称为虚拟机线程栈,它是JVM中每个线程所私有的一块空间,每个线程都会有这么一块空间。虚拟机栈描述了Java中方法执行时的内存模型,即每个方法被执行的时候,线程都会在自己的线程栈中同步创建一个栈帧(Stack Frame),用于存放局部变量表、操作数栈、动态连接和方法出口等信息,每个方法从调用到完成的过程,就对应着一个栈帧在线程栈中从入栈到出栈的过程。
本地方法栈:本地方法栈与虚拟机栈的作用是相似,不同的是虚拟机栈为JVM执行的Java方法服务,而本地方法栈为JVM调用的本地方法服务。
方法区:在JDK 8之前,方法区也称之为永久代,这部分区域与堆一样,是所有线程所共享的,它主要用于存放被虚拟机加载的类型信息、常量、静态变量以及即时编译器编译后的代码缓存等数据。
程序计数器:每个线程都会有自己独立的程序计数器,主要功能就是记录当前线程执行到哪一行指令了,只需要一小块内存空间。
JVM内存参数
JVM内存模型中的这些区域,都是有大小限制的,当然也可以通过JVM提供的参数来设置这些区域所占内存的大小。
下面介绍这些常用参数的含义:
-Xss:表示每个线程栈的大小
-Xms:表示堆空间的初始可用大小,默认为物理内存的1/64
-Xmx:表示堆空间的最大可以大小,默认为物理内存的1/4(这个参数在应用程序中是一定要指定的)
-Xmn:表示新生代(年轻代)的大小
-XX:NewRatio:默认为2,表示新生代占年老代的1/2,占整个堆内存的1/3。
-XX:SurvivorRatio:默认为8,表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。
-XX:MaxMetaspaceSize:设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize:指定元空间触发Full Gc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整:如果释放了大量的空间, 就适当降低该值;如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般推荐将这两个值都设置为256M。