前身:Oak语言(91年开发消费性电子产品)
java me:移动终端java程序
java se:桌面级应用
java ee:企业家应用,包含了许多扩展包
jvm:java虚拟机是运行java程序的,是实现跨平台
jre:包括jvm和java程序所需的核心类库,主要包含lang包
jdk:提供给开发人员使用的,包含了jre、java开发工具(javac.exe、jar.exe…)
95年,1.0发布—–>98年jdk1.2即时编译器——>04年jdk1.5(泛型、枚举等)——>08年sun被oracle收购74亿——>14年1.8lambda表达式,彻底移除永久代——>18Androidjava侵权赔88亿(相当公司白送的)
线程私有的一个字节码行号指令器,当执行native方法时,为空(唯一一个不报空指针异常)
java方法调用时分配空间存储局部变量等,用完就释放
执行native方法时分配空间存储变量
存放对象实例(new 对象,静态对象,1.7加入了静态变量和常量池),也是GC管理的区域,也叫GC堆
存储class、常量、静态变量
jdk1.7时将字符串常量和静态变量移到堆中,jdk1.8废除方法区的概念,改用元空间来存储class
jdk1.8时出现用来代替方法区的
传统的方法区是占用jvm的内存的,是有内存溢出的风险,但是方法区是占用本地内存,是由电脑的内存决定,所以空间很大,基本不会出现内存溢出的情况
属于方法区,即在程序运行时将一些常量加载到池中,常用的一种是:String.intern()
不是jvm运行数据区,默认和最大堆内存一样大,在jdk1.4时,引入NIO,NIO利用native方法堆外内存,可以通过DirectByteBuffer对象对直接内存操作,避免了java堆和native堆的来回复制,即Netty(基于NIO实现的)的零拷贝性质
直接上图:类加载后面会讲,别问我怎么设置对象头啥的,有的东西记住就行了,就像我tm抽你,你问为什么,不为什么,你只要知道你被打了就行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RO9qq6ys-1626579613814)(C:\Users\liuguangcheng\AppData\Roaming\Typora\typora-user-images\image-20210717130246604.png)]
假设堆内存左边是利用了的,右边是空闲的,中间有一个指针,分配内存时,将指针向右挪动对象内存大小,当使用Serial、ParNew收集器时(空间压缩整理)用这种内存分配方式
使用的内存和空闲的交错在一起,就需要维护一个列表,当分配内存时,将一个足够容纳对象大小的内存分配给它就行,当使用CMS(清除)收集器时用这种方式分配内存
后面会讲到垃圾收集器!!
两部分:
不仅包含了自己的数据,还包含了父类继承下来的字段信息
HotSpot虚拟机要求对象大小是8字节的整数倍,不是的话就要通过对其填充来补全
句柄访问:通过句柄池找到对象指针,在通过指针找到对象
直接指针:直接通过对象的指针访问,快速效率高
理解不了?看下面例子
例如:你想找个老婆,你可以找媒婆帮你找一个,你也可以直接找一个,但是呢,结婚以后突然你老婆跑了,那你怎么办,在找一个呗,这时候找媒婆的优势就来了,省时省力,直接叫她在帮你找一个,但是你自己找的老婆呢,就又要自己辛辛苦苦去找
内存泄露:无用对象(垃圾)的内存不释放,导致这部分回收不了,一直占着
内存溢出:在程序申请内存时,内存不够,出现的一种异常,内存泄露的后果就是内存溢出
无用对象的回收叫垃圾回收,怎么判断是不是垃圾呢?
当判定一个对象是不可达时不会被马上回收掉的,必须经过连个阶段
标记需要回收的对象后,统一回收
缺点:
将对象回收后的存活对象复制到另一个等大的内存中,将原来的空间清理,在将两块空间对换,该算法多用于新生代回收算法
缺点:对空间的浪费
优点:解决了内存碎片化的问题,且新生代朝生夕灭,存活对象少,所以效率很高
将对象回收后的空隙进行整理,多用于老年代的回收算法
缺点:整理式对象移动必须全程暂停用户应用程序,否则导致指针混乱,就像你数鸭子,鸭子一直跑,着你怎么数
优点:由于老年代的对象都是老干部,都是经过风吹雨打存活下来的,所以比较少对象会被回收,所以整理的代价还是比较小的
根节点枚举必须暂停所以线程,可达性分析时,并不需要1个不漏的遍历,HotSpot利用了一个OopMap数据结构来存放对象引用,在特定位置记录栈和寄存器的引用
OopMap在特定位置记录时的特点位置就是安全点,所以线程执行到安全点后就开始垃圾收集
在某一段区域的任意地方进行垃圾收集都时安全的
早期的**“单线程”(新生和老年垃圾回收时暂停所以工作)**新生代垃圾回收器,对于运行在客户端模式的虚拟机时一个不错的选择
Serial的多线程(新生代多线程)版本,但是垃圾回收时,依旧需要暂停所以工作
是一个重吞吐量的新生代多线程垃圾回收器
Serial的老年代版本收集器,也是一个单线程
多线程Parallel Scavenge的老年代版本,不能和CMS配合
以最短回收时间停顿的收集器,内存足够时,使用标记清除,不足时用标记整理
面向服务端的全堆垃圾收集器,哪块存放的垃圾最多,回收效益最大,着就是Mixed GC模式
利用染色体指针来实现低延迟垃圾回收器
染色体指针:一种可扩展的存储结构用来记录更多与对象标记、重定向过程相关的数据
特例:解析过程可能会在初始化之后(动态绑定或晚期绑定)
数组类加载:数组类不通过类加载器加载创建,而是直接在内存动态构造
保证class文件字节流符合约束,且不会对jvm造成危害
对static变量初始化并赋原始值(int赋0),而真正的赋值是在构造器中赋值
将常量池的符号引用替换为直接引用
执行类构造器()方法的过程
一个类的全限类名二进制字节流放到jvm外部,让程序直接去获取需要的类
每次都会将类给父类加载器去尝试加载,加载不了才会给子类加载
顺序:启动类加载器——>扩展类加载器——>应用程序类加载器——–>自定义类加载器(必须继承抽象类ClassLoader)
前端编译器(前端优化):对代码基本无优化,但是对编码效率和使用等有比较多的优化手段(语法糖等)
即时编译器(后端优化):几乎大代码有话都放到该阶段完成,运行期优化可使一些可移植class也享受到优化
1个准备、3个处理过程
Java的实现方式是类型擦除式泛型
答:虽然在字节码抹除了泛型,但是在元数据还是保留了泛型信息
作用:将字节码中的**热点代码(多次调用的方法、多次调用的循环体)**编译成本地机器码
一定时间内,调用次数不够即时编译器门槛,就会在垃圾回收时随便计数器减半
2分支作用:
对中间代码或机器码进行代码优化,而不是对源码优化
将方法体替换方法调用,增加了代码量,但是减少了地址引用
对象逃出原本的作用域(return 对象)
表达式计算过,且过程变量不变,则直接使用
1.编译期完成边界检查 2.隐式异常处理
常量迭代、算数聚合、符号合并……
展望替换HotSpot成为一款高效编译、高质量输出、支持即时编译和提前编译、支持不同虚拟机的编译器、继承HotSpot的高质量优化技术
处理器的熟虑与内存的速度根本不是一个等级,这样就限制了我们的效率,所以引入了高速缓存的存储交互(但是带来了缓存一致性问题)
目的:定义程序中各种变量的访问规则
怎么解决缓存一致性问题:
由于编译器的优化和缓存的使用,导致对内存的写入操作不能及时的反应出来,也就是说当完成对内存的写入操作之后,读取出来的可能是旧的内容
对于64位数据类型,虚拟机会将它的读写操作分为3次32位的操作,这样就不能保证操作的原子性了
例如:两个线程同时去对long或double进行读取和修改,可能即得不到原来的值,也得不到修改后的值
实际开发:除非你明确知道该类型变量有线程竞争,否则没有必要去加volatile关键字
Thread的所以关键方法都声明位Native,即本地方法
当一个线程操作变量时,其他的线程不会对该变量的操作造成影响
JDK5—->JDK1.6加入了很多的锁优化手段:适应性性自旋、锁消除、锁膨胀、轻量级锁、偏向锁
默认开启,在获取锁失败后,会自旋重试来获取锁
对被检测导明确不会存在锁竞争的锁进行锁消除来提高运行效率,锁消除的根据还是逃逸分析技术(前面讲过了)
一串零碎的锁操作对同一个对象,加锁的范围扩展导整个操作序列的外部
对象头(mark word)记录一些的对象的非数据信息,里面就包含了锁标志等,轻量级锁锁升级后会变成重量级锁
将mark word内的偏向模式设为1,标志位设位01,则是偏向锁状态,锁竞争会锁升级位轻量级锁
具体的锁和并发可看JUC
final修饰的变量、java.lang.Number的部分子类(Long、Double…)
2. 绝对线程安全:不管什么环境,都是线程安全的
3. 相对线程安全:大部分线程安全集合类(Vector、hashMap….)等
JDK5—->JDK1.6加入了很多的锁优化手段:适应性性自旋、锁消除、锁膨胀、轻量级锁、偏向锁
默认开启,在获取锁失败后,会自旋重试来获取锁
对被检测导明确不会存在锁竞争的锁进行锁消除来提高运行效率,锁消除的根据还是逃逸分析技术(前面讲过了)
一串零碎的锁操作对同一个对象,加锁的范围扩展导整个操作序列的外部
对象头(mark word)记录一些的对象的非数据信息,里面就包含了锁标志等,轻量级锁锁升级后会变成重量级锁
将mark word内的偏向模式设为1,标志位设位01,则是偏向锁状态,锁竞争会锁升级位轻量级锁
具体的锁和并发可看JUC