深入理解Java虚拟机(下)

一, 类加载的时机
类的生命周期包括:加载,连接(验证,准备,解析) ,初始化,使用,卸载,总共七个阶段。
什么情况需要开始第一个阶段:加载?java虚拟机并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把我。但是对于初始化阶段,有且只有5种情况必须对类进行初始化(加载,验证,准备自然需要在此之前开始):

  1. 遇到new,getstatuc,putstatic或invokestatic这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这四条指令常见java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段,以及调用一个类的静态方法的时候。
  2. 对类进行反射调用的时候
  3. 初始化一个类,先触发其父类的初始化
  4. 虚拟机启动时,先初始化主类(包含main方法的类)
  5. 当使用JDK 1.7的动态语言支持时,如果一个java.lang .invoke.MethodHandle实例最后
    的解析结果REF_getStatic、REFputStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

被动引用:1通过子类引用父类的静态字段,不会导致子类初始化。2通过数组定义来引用类,不会触发此类的初始化。3常量在编译阶段会存入调用类的常量池中,本质上并没有引用到定义常量的类,因此不会触发定义常量的类的初始化。

1.1加载:是类加载的一个阶段。虚拟机要完成以下三件事情:
1) 通过一个类的全限定名获取定义此类的二进制字节流
2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3) 在内存中生成一个代表这个类的java.lang.Class对象作为方法区这个类的各种数据的访问入口
其中二进制字节流并不一定要从一个Class文件中获取,可以从jar,war中读取。
1,2验证
是连接阶段的第一步,这一阶段的目的是为了确保Class文件字节流中包含的信息不和当前虚拟机的要求,并且不会危害虚拟机自身的安全。
包括:1)文件格式验证:是否以魔数开头,主次版本号是否在当前虚拟机处理范围之内等
2)元数据验证:这个类是否有父类,这个类是否继承了不允许被继承的类。
3)字节码验证
4)符号引用验证
1.3准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这时候进行内存分配的仅仅包括类变量-(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。这里说的初始值时数据类型的零值,比如:
Public static int value=123
在准备阶段后value被赋值为0而不是123.
1.4解析:
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程
符号引用:以一组符号来描述所引用的目标,符号柯一师任意形式的字面量,只要使用时能无歧义的定位到目标即可。
直接引用:可以是直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。
1.5初始化:
类初始化阶段是类加载过程的最后一步,到了初始化阶段,才开始真正执行类中定义的java程序代码(或者说是字节码)
2类与类加载器
比较两个类是否相等,只要在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
2.1双亲委派模型:
从虚拟机的角度来讲,只存在两种不同的类加载器,一种是启动类加载器(bootstrap classloader),另一种是所有其他的类加载器,这些类加载器都由java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader.
从java开发人员的角度看,可以划分为(自顶向下::)
启动类加载器(bootstrap classloader)------扩展类加载器(Extension ClassLoader)-----应用程序类加载器(application ClassLoader)------自定义类加载器。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会去自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终偶读应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求,子加载器才会尝试自己去加载。
如果没有双亲委派模型,当加载用户自己写的某个类Object时,系统将会出现多个不同的Object类,应用程序也会变得一片混乱。

二:高效并发

  1. java内存模型:
    java内存模型的主要目标是定义程序中的各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节
    java内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的祝所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量,不同的线程之间也无法直接访问对方工作内存中的变量。
    2对于volatile变量的特殊规则
    当一个变量定义为volatile,它将具备两种特性,第一是保证此变量对所有线程的可见性。指当一条线程修改了这个变量的值,将这个变量回写到系统内存,使其他线程缓存了该内存地址的数据无效(在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了)。
    第二个特性是禁止指令重排序优化。
    由于虚拟机对锁实行的许多消除和优化,很难量化的认为volatile比synchronized快多少。Volatile变量读操作性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢一些,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
    2可见性
    除了volatile之外,java还有两个关键字能实现可见性,即synchronized和final,被final修饰的字段在构造器中一旦初始化完成,那在其他线程中就能看见final字段的值。
    3线程状态转换
    Java语言定义了5中线程状态
    1) 新建(new)
    2) 运行(Runable)包括了操作系统线程状态中的running和ready。处于此状态的线程有可能在执行,也有可能正在等待着cpu给它分配执行时间。
    3) 无限期等待(waiting):处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显示地唤醒。以下方法会让线程陷于无限期的等待状态:
    没有设置timeout参数的object.wait()方法
    没有设置timeout参数的thread.join方法
    3.1)限期等待(timed waiting):处于这种状态的线程也不会被分配CPU执行时间,在一定时间之后他们被系统自动唤醒:
    Thread.sleep()方法
    设置了timeout参数object.wait()方法
    设置timeout参数的thread.join方法
    4) 阻塞(blocked)与等待状态的区别是阻塞状态是等待获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而等待状态则是等待一段事件,或者唤醒动作的发生。在程序进入同步区域的时间,线程将进入这种状态
    5) 结束(terminated)

三:线程安全
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值