~1、深入理解java虚拟机-jvm高级特性与最佳实践
prv:1.6版本的新生代老生代都在堆中,永久代在方法区中,但是1.7之后常量池在堆中
如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowerror(当前大部分虚拟机栈可以动态扩展),如果扩展时无法申请到
足够内存,将报outofmeoryerror
程序计数器是唯一一个不会报oom的。
方法区、堆都是线程共享的,其他都是线程私有的,私有也就是说各线程之间数据独立存储互不影响,称这类内存区域为‘线程私有’的内存
栈帧是方法运行时的基础数据结构。
逃逸分析: 转载链接:https://blog.csdn.net/w372426096/article/details/80938788
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他
地方中,称为方法逃逸。使用逃逸分析,编译器可以对代码做如下优化:
一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
(网络博文摘-借鉴)逃逸分析:就是方法内的对象,可以被方法外所访问。
栈上分配:就是把没发生逃逸的对象,在栈分配空间。(一般对象分配空间是在堆)逃逸
二者联系:jvm根据对象是否发生逃逸,会分配到不同(堆或栈)的存储空间。
能在方法内创建对象,就不要再方法外创建对象。毕竟这是为了GC好,也是为了提高性能。
三、同步消除
同步消除是java虚拟机提供的一种优化技术。通过逃逸分析,可以确定一个对象是否会被其他线程进行访问
如果对象没有出现线程逃逸,那该对象的读写就不会存在资源的竞争,不存在资源的竞争,则可以消除对该对象的同步锁。
通过-XX:+EliminateLocks可以开启同步消除,进行测试执行的效率
我们可以通过测试结果看出,如果开启了同步消除,在开启同步消除的执行效率比没有开启同步消除的高。
方法区:类信息,常量,静态变量,即时编译器编译后的代码
运行时常量池属于方法区中
分配对象的算法根据垃圾收集器来定,有指针碰撞和空闲列表。前者假设内存空间连续完整,每次分配一个对象的内存只需要指针往空闲
区域挪动与对象大小相等的距离即可,空闲列表则是表示内存不连续完整,需要维护一个可用内存列表,从而分配空间。
对象在内存中存储的布局可以分为3个区域:对象头、实例数据、对齐填充
对象的访问定位:用句柄或者是指针。
句柄池(也在堆中)包含到对象实例数据的指针(指向堆)以及到对象类型数据的指针(指向方法区)
直接指针:(堆)包含到对象类型数据的指针(指向方法区)、对象实例数据
句柄好处是reference(栈中存放的对象引用)存储的是稳定的句柄地址,对象被移动reference不用修改
直接指针的好处就是速度快,节省了一次指针定位的时间开销,这类开销积少成多之后是很可观的执行成本。
将堆最小值参数-Xms跟最大值参数-Xmx设置成一样就可避免堆的自动扩展
oom
如果是内存泄漏,就要用工具查看泄露对象到GC Roots的引用链,于是就能找出泄露对象是通过怎样的路径与GC Roots相关联并导致
垃圾收集器无法自动回收他们,掌握了泄露对象的类型信息以及GCRoots引用链的信息,就能比较准确的定位出泄露代码的位置
如果不是内存泄露就应该检查堆参数与机器物理内存对比看是否可以调大,从代码上看是否存在某些对象生命周期过长,持有状态时间
过长等情况,尝试减少程序运行期的内存。
内存溢出oom可能是加载了容量超大的数据,也可能是按照堆栈日志分析找出程序错误点
内存泄露就是循环引用,a引用b,b引用a,导致垃圾回收器无法回收
本地直接内存溢出
DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与java堆最大值(-Xmx指定)一样
由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果读者发现OOM之后Dump
文件很小,而程序中又直接或间接使用了NIO,就可以考虑检查下是不是这方面原因。
Unsafe类的getUnsafe()方法限制了只有引导类加载器才会返回实例,也就是设计者希望只有rt.jar中的类才能使用Unsafe功能
,因为使用了DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过
计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()。
各区域内存溢出整理
程序计算器
线程私有。当前线程所执行的字节码的行号指示器。字节码解析器通过改变计数器的值来选取下一条需要执行的字节码指令。
虚拟机栈
线程私有。生命周期跟线程一致。描述的是java方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧TODO 40页未整理完