一款名为Exact VM的虚拟机,采用准确是内存管理,指虚拟机可以知道内存中某个位置的数据具体是什么类型。
如内存中有一个32bit的整数123456,虚拟机有能力分辨出它是指向一个123456的内存地址的引用类型还是一个数值为123456的整数
描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法对应一个栈帧
活动线程中,只有栈顶的帧才有效,称为当前栈帧
-XX:+/-UseTLAB
参数设定是否使用 TLAB。java.lang.OutOfMemoryError: Java heap space
主要发生在Java堆和方法区中
从GC Root对象作为起点开始向下搜索,走过的路径称为引用链
从GC Root开始,不可达的对象被判为不可用
可作为GC Root的对象:
栈中:
方法区中:
可总结为:
1,所有老年代对象
2,所有全局对象
3,所有jni句柄
4,所有上锁对象
5,jvmti持有的对象
6,代码段code_cache
7,所有classloader,及其加载的class
8,所有字典
9,flat_profiler和management
10,最重要的,所有运行中线程栈上的引用类型变量
作者:cao
链接:https://www.zhihu.com/question/33093157/answer/89516243
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
对象在判为不可达后,仍有自救方法
-XX:PretenureSizeThreshold
:单位是字节,规定大对象大小
-XX:MaxTenuringThreshold
设定值后,会被晋升到老年代,-XX:MaxTenuringThreshold
默认为 15;Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件中,中间没有任何分隔符。Java 虚拟机规范规定 Class 文件采用一种类似 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表,我们之后也主要对这两种类型的数据类型进行解析。
_info
结尾。此阶段第一步不严格要求从class文件中获取,还可以是压缩包,网络数据等,只要是二进制字节流就可以,灵活性很高
加载阶段结束后,外部二进制字节流就按照虚拟机设定的格式存储在方法区之中
类型数据存在方法区中后,Java堆内存中实例化一个java.lang.Class类对象,作为访问方法区中类型数据的对外接口
很重要,但不是必不可少,如果代码都被反复使用验证过,可以关闭部分验证措施
正式为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段
public static int value = 123;
此时value初始值为 0 ,而不是123,到初始化阶段才会赋值执行类构造器()方法的过程
将”通过一个类的全限定名获取描述该类的二进制流“这个动作放到Java虚拟机外部实现,便于让应用程序决定如何获取所需的类
任意一个类,由加载它的类加载器和它本身一起共同确立其在Java虚拟机中的唯一性,只有加载类的类加载器是同一个,才认定这两个类相等
Java虚拟机角度来看,只存在两种不同的类加载器:
工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成
实现:
parent.loadClass(name,false)
线程上下文类加载器:
父类加载器请求子类加载器去完成类加载
OSGi…
JDK9中引入
**模块化关键目标:**可配置的封装隔离机制
只要存放在类路径上的jar文件,无论是否包含模块化信息,都会被当作传统jar包处理
只要存放在模块路径上的jar文件,即使没使用JMOD后缀,或不包含模块化信息,仍然会被当作一个模块对待
Amdahl定律通过系统中并行化与串行化的比重来描述多处理器系统能获得的运算能力**
多个处理器每个处理器都有自己的高速缓存,而它们又共享同一主内存,这叫共享内存多核系统。
多个缓存都涉及到同一内存,但多个缓存的数据可能不一致,以谁的为准?
实现让Java在不同平台下达到一致的内存访问效果,屏蔽硬件和操作系统的内存访问差异
目的:定义程序中各种变量的访问规则
关注虚拟机中把变量值存储到内存;从内存中取出变量值
变量(不同于Java编程中的变量):
规定所有变量都存储在主内存,类比主内存
每条线程有自己的工作内存,类比高速缓存
线程的工作内存中保存了被该线程使用的变量的主内存副本
线程对变量的所有操作都必须在工作内存中,不能直接读写主内存中的数据
不同线程也无法访问对方工作内存中的变量
下面8个操作都是原子的,不可再分的
注意:
适用场景:
原因:Java运算操作符并不是原子操作
两项特性
long和double的非原子性协定:
允许没有被volatile修饰的64位数据读写操作划分为两次32位的操作来进行
有序性
两项操作之间的偏序关系
A先行发生于B,那么B操作之前,A操作产生的影响能被B观察到
Thread:join();Thread:isAlive()
线程是比进程更加轻量级的调度执行单位
把一个进程的资源分配和执行调度分开,各个线程可以共享进程资源,又可以独立调度
一般都是native方法,在本地方法栈中,无法保证平台无关
三种方式
内核线程实现
整合了上面两个实现的优点,互补了缺点
取决于具体虚拟机
系统为线程分配处理器使用权的过程
Object::wait()
方法Thread::join()
方法LockSupport::park()
方法Thread::sleep()
方法Object::wait()
方法Thread::join()
方法LockSupport::parkNanos()
方法LockSupport::parkUtil()
方法1:1内核线程切换调度成本高,系统能容纳的线程数量有线
单体应用允许请求花费时间,切换线程成本无伤大雅
但现在微服务架构下执行时间短、数量多,线程切换开销可能接近于计算开销,浪费严重
为什么内核线程调度切换起来成本高?
协程更加轻量级
需要在应用层实现的东西更多(调用栈,调度器等)
当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,就称这个对象是线程安全的
线程安全不是非真即假的,它指的是安全程度
互斥同步
同步:多个线程并发访问共享数据时,保证共享数据在同一时刻只被一条(或一些,当使用信号量的时候)线程使用
互斥:实现同步的一种手段,临界区、互斥量和信号量都是常见的互斥实现方式
使用synchronized的注意点:
重入锁(ReentrantLock)增加了一些功能如下
为什么依然选用synchronized而不是重入锁?
非阻塞同步
互斥同步,也叫阻塞同步,因为
进行线程阻塞和唤醒会带来性能开销
悲观的,默认认为不加锁就会出现问题,导致
等开销
非阻塞同步:基于冲突检测的乐观并发策略,先进行操作,如果共享数据被争用,再进行补偿措施
需要硬件支撑
硬件保证语义上看起来需要多次操作的行为可以只通过一条处理器指令就能完成,如
测试并设置(Test-and-Set)
获取并增加(Fetch-and-Increment)
交换(Swap)
比较并交换(Compare-and-Swap)CAS
加载链接/条件储存(Load-Linked/Store-Conditional)
Java中CAS的过程:
内存位置(Java中简单理解为变量内存地址,用V表示)
旧的预期值A,准备设置的新值B
当且仅当V符合A时,处理器才会用B更新V,否则不执行更新,最终无论是否更新都返回V的地址
CAS 的ABA问题:
同步和线程安全没有必然联系,同步只是保障在共享数据争用时的正确手段,如果方法不涉及共享数据,就不需要任何同步措施保证其正确性
自旋锁
自适应自旋
锁消除
锁粗化(膨胀)
轻量级锁
设计初衷:没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗
偏向锁