1.说一说JVM的内存模型?
通过工具我们看一下JVM的内存
对应上图我们画出JVM的 内存模型
2.JAVA类加载的全过程是什么样的?什么是双亲委派机制?有什么作用?一个对象从加载到JVM,再被GC清楚,发生了什么?
java的类加载器(其实就是类的加载机制):(1.)AppclassLoder (2)ExtclassLoder (3)BootstrpclassLoder(底层是由C++写的)
解释:
AppClassLoder 是加载我们自己写的类的息,
ExtClassLoder,ExtClassLoderbootstrpclassLoder的底层是又C++实现的,他是加载我们的基础类信息,比如String,Object等等 …
java中的类加载器:(类的继承关系)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//Java中每个加载过的类都会存在在缓存中,所以在加载的过程中,他首先会去查找缓存
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;
}
}
源码解析 ->了解双亲委派机制以及类的加载过程
//Java中每个加载过的类都会存在在缓存中,所以在加载的过程中,他首先会去查找缓存
Class<?> c = findLoadedClass(name);
//如果缓存中没有找到要加载的类的信息,会去看他的父类,然后通过父类加载
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果没要找到父类的类类加载信息才回去去调用BootstrapClassLoder去自己加载,
c = findBootstrapClassOrNull(name);
}
双亲委派机制就是向上委托查找,找不到再向下委托查找.双亲委派机制存在的意义就是保护我们Java底层的类,防止底层的类被覆盖,比如我们手写了一个String类,但我们要加载的是java.long.String .所以一开始我们并不会去加载我们的classpath下面查找,而是向上委托查找.
类加载过程:
加载->连接->初始化
加载:把Java的字节码数据加载到JVM内存当中,并映射成JYM认可的数据结构。
连接:分为三个小的阶段:
1、 验证:检查加载到的字节信息是否符合JVM规范。
2、准备:创建类或接口的静态变量,并赋初始值半初始化状态
3、解析:把符号引用转为直接引用
初始化:
java中创建对象的过程:
类加载检查:
虚拟机遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已经被加载过,解析和初始化过(类的初始化和对象的初始化不一样),如果没有就先去执行类的的加载
分配内存:
在类的检查通过后,接下来虚拟机将为新生的对象分配内存,对象所需要的内存大小,在类加载完成后便可以确定了,在堆中划分一块内存确定的区域.分配的方式主要有"指针碰撞","空闲列表"两种,选择那种分配方式主要由Java的堆是否工整,Java的堆是否工整由采用的垃圾收集器是否带有压缩整理的功能决定的.
设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump thePointer)。如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(FreeList)。
半初始:
分配完内存后,虚拟机需要将分配到的内存空间初始化为0值(不包括对象头)
设置对象头:
半初始化完成后,虚拟机要对对象的头进行设置,例如这个对象是那个类的实例,如何才能找到类的元数据信息,对象的HashCode,对象的GC年龄分代,当前虚拟机的运行状态,是否启用偏向锁.
执行inint方法:
执行完上面的过程,从虚拟机的角度,一个Java对象的创建过程已经完成了,但是从程序的角度才刚刚开始,inint没有执行,所有的字段都是0,执行inint方法后给我们的对象完成初始化,这样一个Java对象才真正的产生.
3. 对象的访问定位有那两种方式?
建立对象的目的就是为了使用对象,Java程序通过栈上的reference数据来操作堆上的具体对象,主流的访问方式有两种,1 . 使用句柄 2. 直接指针
句柄:
Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。
直接指针
如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而引用中存储的直接就是对象地址。
优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。(例如HotSpot)