类加载的过程
ClassLoader只负责class文件的加载不负责是否可以运行
加载:将字节码文件(class文件)加载到内存中
1、通过一个类的全限定名获取此类的二进制字节流
2、将字节流所代表的静态存储结构转化为方法区(jdk7以前叫永久代8开始叫元空间)的运行时数据结构(类模版)
3、在内存(堆)中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问的入口
验证:验证文件格式、元数据验证、字节码验证(开头为cafebabe)等
准备:为静态变量(类变量)分配内存空间,赋默认值0…
静态变量会分配到方法区,不会对成员变量分配。成员变量会随着对象分配到堆空间中,(不含final修饰的static,final修饰的在编译期间就分配了)
解析:将常量池中的符号引用转为直接引用
符号引用就是用一组符号来描述所引用的目标,直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化:初始化阶段就是执行类构造器方法
clinit方法就是给静态变量赋值(包括初始化静态代码块)的,jvm使用同步锁防止多个线程都去赋值
clint这个方法不需要定义,是javac编译器自动收集类中的所有类变量赋值动作和静态代码块中的语句合并而来。
构造器方法中指令按语句在源文件中出现的顺序执行。
若该类具有父类jvm会保证子类的
执行一个类的
类加载器
jvm支持两种类加载器 :
引导类加载器 (Bootstrap ClassLoader)和自定义类加载器
引导类加载器 (Bootstrap ClassLoader):是由c/c++实现的,嵌套在JVM内部 只负责加载java核心类库并不继承于java.long.ClassLoader,没有父加载器。扩展类加载器和应用程序类加载器都是由引导类加载器加载的。出于安全考虑,Bootstrap加载器只加载包名为java、javax、sun等开头的类
扩展类加载器(Extension ClassLoader):父类(包含关系/上层)加载器为Bootstrap类加载器,从JDK的安装目录的jre/lib/ext子目录下加载类库,如果用户创建的 jar放在此目录下也会由Ext加载器加载
应用程序类加载器(AppClassLoader):父类(包含关系/上层)加载器为Extension类加载器,负责加载环境变量classpath或系统属性,一般来说java应用的类都是由AppClassLoader加载的
自定义类加载器:在java日常应用程序开发中,类的加载几乎是由以上三种类加载器相互配合执行的,在必要时开发者可以自定义类加载器来定制类加载方式
为什么自定义类加载器:隔离加载类、修改类加载方式、扩展加载源、防止源码泄漏…
可以通过继承ClassLoader重写findClass实现自定义类加载器
对于用户自定义类来说,默认使用应用程序类加载器(AppClassLoader)加载
java的核心类库都是使用引导类加载器加载的
双亲委派机制
java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类的时候才会将它的class文件加载到内存生成class对象。而且加载到某个类class文件时,java虚拟机采用的是双亲委派模式,即把请求交由父类处理,他是一种任务委派模式
在jvm中表示两个class对象是否为同一个对象的两个条件:
1,全限定名一样(类名和包名)
2,加载这个类的类加载器相同
也就是说 即使两个类class对象来源于同一个class文件,被同一个虚拟机所加载,但加载他们的类加载器不同,那么这两个类也是不想等的。
类的主动使用和被动使用主动使用:
1,创建类的实例
2,访问某个类或接口的静态变量,或对静态变量的赋值
3,调用类的静态方法
4,反射
5,初始化一个类的子类
6,java虚拟机启动时被标明为启动类的类
7,jdk7开始提供的动态语言支持:
java.long.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化
除了以上其中情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化(clinit)**

一个进程只有一个方法区和一个堆空间
每个线程都有自己的计数器、本地方法栈、虚拟机栈、所有线程共用一个方法去和堆空间
PC寄存器
pc寄存器中存储的是每个线程执行的指令地址,每个线程独有的。
占用内存小,运行快。当多个线程来回切换的时候就是靠pc寄存器来识别接下来要运行的指令(代码)
栈
栈是运行时的单位,而堆是存储的单位
即:栈解决程序的运行问题,即程序如何执行,或者说是如何处理数据。
堆解决的是数据存储问题,即数据怎么放、放在哪
java虚拟机栈:
java虚拟机栈早期也叫java栈,每个线程创建的时候都会创建自己的一个虚拟机栈,其内部保存一个个栈帧,对应着一次次的java方法调用
生命周期:生命周期与线程一致
作用:主管java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
栈是一种快速有效的分配存储方式,访问速度仅次于pc暂存器(程序计数器)JVM对栈的操作只有两个:每个方法执行结束,伴随着进栈,执行结束后出栈。对栈来说不存在垃圾回收问题。
栈帧:每个栈帧中存储着:
局部变量表(Local Variables)
操作数栈 (Operand Stack)(或表达式栈)
动态链接(Dynamic Link-ing)(或指向运行时常量池的方法引用)
方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
一些附加信息
局部变量表:
是一个数字数组,主要用于存储方法参数喝定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用以及返回地址。(会用数字代指)
由于局部变量表是建立在线程栈上,是线程私有数据,因此不存在数据安全问题
局部变量表所需的容量大小是在编译器确定下来的,一旦确定就不会更改。
方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。
局部变量表中的变量旨在当前方法中调用有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。
Slot(变量槽):局部变量表中最基本的存储单位 32位以内的类型只占用一个slot,64位的类型(long和double)占用两个slot
如果当前帧是由构造方法或者非静态方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数顺序继续排列。
局部变量表中的变量是垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收
操作数栈(Operand Stack)
操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
操作数栈会执行复制、交换、求和等操作
jvm调用方法指令:
虚方法与非虚方法:
如果在编译器就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法成为非虚方法
静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。
其他方法称为虚方法。
jvm调用非虚方法的指令:
invokestatic(调用静态方法)
invokespecial(调用
invokevirtual(调用所有虚方法)
invokeinterface(调用接口方法)
动态调用指令:
invokedynamic (动态解析出需要调用的方法,饭后执行)
虚拟机栈的面试题:
举例栈溢出的情况?(StackOverflowError)
- **通过-Xss设置栈的大小;OOM;递归调用**
调整栈大小,能保证不出现溢出吗?
- **不能,但是有时可以解决栈溢出问题**
分配的栈内存越大越好吗?
- **栈是线程私有的,如果设置每个栈空间太大会导致可分配线程数量变少,甚至出现OOM**
垃圾回收是否会设计到虚拟机栈?
- **虚拟机栈不会出现gc,垃圾回收主要作用在堆空间**
方法中定义的局部变量是否线程安全?
- **具体问题具体分析**
本地方法:
被native修饰的方法称为本地方法,与java环境外交互,这是本地方法存在的主要原因。
本地方法栈:
本地方法栈用于管理本地方法的调用,允许被实现成固定或者可动态扩展的内存大小(在内存溢出方面是相同的)本地方法是用c语言实现的,他的具体做法是Native Method Stack 中登记native方法,在Execution Engine执行时加载本地方法库。
堆:
-Xms40m //初始内存(年轻代+老年代)
-Xmx256m //最大内存(年轻代+老年代)
-Xmn16m //最小内存(年轻代大小)
-XX:NewRatio //设置新生代与老年代的比例默认是2
-XX:SurvivorRatio //设置新生代中Eden区与幸存者区的比例 默认8:1:1
查看堆内存试用情况:cmd命令行:jstat -gc 进程 或者 -XX:+PrintGCDetails
一个JVM实例只存在一个堆空间,堆在JVM启动的时候即被创建,大小也就确定了。
堆中有一块空间是线程私有的:缓冲区(堆是所有线程共享的 就会存在线程安全问题,如果都加同步锁的话效率太低所有产生了缓冲区)
栈中存储的是栈帧 栈帧中的局部变量表指向堆中的对象实例 对应的类的结构和常量池在方法区
在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
堆内存逻辑上分为新生代、老年代、元空间(jdk8+)
年轻代与老年代
eden(伊甸园区)满了会触发YGC 没有被gc的进入幸存者0区(幸存者区满了不会触发YGC) 经历15次没被gc的进入老年代(阈值可设置最大为15)
几乎所有的Java对象都是在Eden区被new出来的 绝大部分的Java对象的销毁都在新生代进行
总结:
针对幸存者s0,s1区的总结:复制之后有交换,谁空谁是to
关于垃圾回收:频繁在新生代收集,很少在老年代收集,几乎不元空间收集。
垃圾回收机制
Full GC :整堆回收 收集整个java堆和方法区的垃圾收集
Minor GC : 新生代收集(Young GC)只收集伊甸园区和s0,s1区
当年轻代(伊甸园区)空间不足的时候,就会触发Minor GC,GC会连同s1/s0(幸存者区)一起回收
Major GC : 老年代收集(Old GC)只负责老年代的垃圾收集 (很多时候Major GC和Full GC会混淆使用,需要具体分辨是老年代回收还是整堆回收)
出现Major GC通常会伴随着至少一次的Minor GC 当老年代空间不足时,会先尝试触发Minor GC。如果空间还不足就会触发Major GC
Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长
如果Major GC后,内存还不足,就报OOM了
