jvm执行程序过程
- 加载.class文件
- 管理并分配内存
- 垃圾回收
类加载过程
- 加载:将字节码的静态数据结构加载到方法区形成运行时数据结构,并在堆中生成这个类的class对象,作为方法区数据的访问入口
- 链接:
- 验证:确保加载的类信息符合jvm规范,不会危害虚拟机的安全。
- 准备:仅为类的静态变量(static filed)在方法区分配内存,并赋默认初值(0值或null值)。如static int a = 100;静态变量a就会在准备阶段被赋默认值0。而静态常量(static final filed)会在准备阶段赋程序设定的初值。
- 解析: 将常量池的符号引用替换为直接引用的过程。(A.a = “Hello”替换为A.a指向“Hello”的地址)
- 初始化:先递归初始化父类。执行类构造器clinit()方法,合并静态变量与静态语句块。(虚拟机保障静态变量静态块的线程安全)
- Java虚拟机规范没有强制性约束在什么时候开始类加载过程,但是对于初始化阶段,虚拟机规范则严格规定主动引用一定会发生初始化,被动引用不会发生初始化
主动引用与被动引用
- 主动引用
- new()
- 调用静态但非常量成员及方法
- 反射调用
- main()所在类
- 父类
- 被动引用
- 引用常量池
- 通过数组定义 A[] A=new A[10];
- 访问一个静态变量时,只有真正声明这个变量的类会被初始化。例如子类B extends A,A中有static int a;那么B.a,B不会被初始化
jvm内存模型
PC:线程私有,为了线程切换后能恢复到正确的执行位置。如果线程执行的是Java 方法,计数器记录正在执行的虚拟机字节码指令地址;如果执行的是Natvie 方法,计数器值为空(Undefined)。
虚拟机栈:线程私有,生命周期与线程相同。每个方法被执行时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息. 每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度)
本地方法栈:线程私有,为虚拟机使用到的Native 方法服务。
堆:存放对象实例,被所有线程共享,也是垃圾收集器管理的主要区域。堆的大小可以通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。
方法区:被所有线程共享,线程安全。用于存储常量池,类相关信息(类型信息,字段信息,方法信息,静态变量),指向ClassLoader的指针,指向Class对象的指针,方法表等。
垃圾回收
- 标记-清除算法
- 复制算法
- 标记-整理算法
只有方法区和堆需要进行GC。
分代回收算法:内存被分为新生代、旧生代、持久代、他们所用的GC算法不同。
新生代:新生代由Eden Space和两块相同大小的Survivor Space构成,默认比例为8:1:1,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及SurvivorSpace的大小。
对象首先在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到旧生代。
虚拟机对新生代的垃圾回收称为Minor GC,次数比较频繁,每次回收时间也比较短。新生代使用复制算法和标记-清除垃圾收集算法。旧生代:旧生代中存放生命周期较长的对象,例如缓存对象,新建的对象也有可能直接进入旧生代,主要有两种情况:1、大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在旧生代分配。2、大的数组对象,且数组中无引用外部对象。旧生代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。旧生代采用标记-整理垃圾回收算法。虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。
- 永久代:永久代垃圾回收比较少,效率也比较低,也使用标记-整理算法进行垃圾回收,java虚拟机参数-XX:PermSize和-XX:MaxPermSize可以设置永久代的初始大小和最大容量。
类加载器
层次结构:
类加载器之间的父子关系通过组合实现。
- Bootstrap ClassLoader:负责加载Java的核心类库,%JAVA_HOME%/lib)目录下的rt.jar(包含System、String这样的核心类)。它由C/C++实现,不是java.lang.ClassLoader的子类。
classloader相关方法:
ClassLoader getParent()
Class<?> loadClass(String name)
Class<?> findClass(String name)
Class<?> defineClass(String name, byte[] b, int off, int len)
void resolveClass(Class<?> c)
双亲委派机制
- 保证java核心类库的类型安全
- 1、”A类加载器”加载类时,先判断该类是否已经加载过了;
2、如果还未被加载,则首先委托其”父类加载器”去加载该类,这是一个向上不断搜索的过程,当A类所有的”父类加载器”(包括bootstrap classloader)都没有加载该类,则回到发起者”A类加载器”去加载;
3、如果还加载不了,则抛出ClassNotFoundException。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}`
自定义类加载器
class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c=findLoadedClass(name);
if(c!=null){
return c;
}else{
ClassLoader parent = this.getParent();
try {
c = parent.loadClass(name);
} catch (Exception e) {
}
if(c!=null){
return c;
}else{
byte[] classdata=getClassData(name);
if(classdata==null){
throw new ClassNotFoundException();
}else{
c=defineClass(name,classdata, 0, classdata.length);
}
}
}
return c;
}
private byte[] getClassData(String name) {
InputStream in=null;
ByteArrayOutputStream baos=new ByteArrayOutputStream();
try {
in= new FileInputStream(name);
byte[] buffer=new byte[1024];
int temp=0;
while((temp=in.read(buffer))!=-1){
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
类相等的判定条件
- 两个类来自同一个Class文件
- 两个类是由同一个虚拟机加载
- 两个类是由同一个类加载器加载
线程上下文加载器:
Thread.currentThread().setContextClassLoader(new MyClassLoader());
参考文章1-JVM内存模型
参考文章2-JVM内存模型
参考文章3-JVM垃圾回收机制
参考文章4-JVM运行时数据区
参考文章5-JVM类加载器机制与类加载过程