内存结构
堆区和方法区是线程共享的
类加载子系统
JVM不管你的字节码文件是通过什么语言编写的,只要生成的字节码文件符合JVM的规范,那么就可以执行。
类加载器负责把字节码文件加载到内存,是否可以运行,由执行引擎来决定。
类加载过程
1、加载阶段(区别于整体的类加载)
掌握:类加载器通过全限定类名获取类的二进制字节流。生成类的Class对象是在这个阶段出现的。
2、链接阶段
在链接阶段中的准备阶段,只会给类变量分配内存并设置初始值,到了初始化阶段才会赋给它实际定义的值
而对于final类型的static变量,实际是一个常量,它在编译成class文件的编译期就已经分配内存了,这里的准备阶段只是确定地显式说明一下。
当然,实例变量也不会在准备阶段初始化,因为对象还没有产生。
类变量会分配到方法区,实例变量会随对象分配到java堆
通过Binary Viewer工具查看class文件
一个JVM能运行的class文件都是以CAFEBABE开头的
3、初始化阶段,理解记忆
clinit类的构造器方法,区别于java类中的构造器
clinit不需要程序员定义。是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并来的。
构造器中指令按源文件中出现顺序执行。
使用Binary Viewer 把字节码文件打开,这其中的字节码一个个翻译后就是ByteCode viewer的结果
类成员number声明在下边,赋值在上边,这种写法也是可以的
因为:在链接阶段的准备环节,就给static类成员进行了分配内存并赋值为0,然后才从上到下赋值并覆盖,最终结果是10
变量number的声明发生在后边,在前边的静态代码块中可以赋值,但是不可以调用
下边代码,因为此时没有类成员和静态代码块,所以不需要clinit,所以不产生clinit
此时如果加上一个类成员
就产生clinit方法.
所以说clinit只有在存在类成员的时候才会出现,只负责出final修饰的静态成员和静态代码块
上边就是构造器方法
子类的加载是在父类加载后
在多线程下,一个类只能被加载一次
上边代码,两个线程,2先抢到,在执行到DeadThread类时出现死循环,一直跳不出,那线程1就没法进行,也就是说,DeadThread类只被加载了一次。在操作这个对象的时候存在一个锁的过程,只能由一个线程操作
加载同一个线程,会导致其他线程出现阻塞的状态,要避免
重新理解这个类加载过程