文章目录
请你谈谈jvm的理解?
java->class->jvm
jvm调优都是在调方法区(1%)和堆(99%)
第三方插件一般是在执行引擎上改动
jvm的位置
jvm的体系结构
jvm的类加载器
包括加载、链接、初始化
实例的引用(名字)放在java栈,对象放在堆里面
1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序加载器
双亲委派机制
保证安全
原理:app->exc->boot(最终执行)
扩展类加载器和根加载器都没有的情况下,就会去找应用程序加载器
沙箱安全机制
native
pc寄存器
程序计数器
方法区
method area:一种特殊的堆
方法区存放在永久代区。
方法区:线程共享的。
java栈
与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
局部变量表主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
Java 虚拟机栈会出现两种错误:StackOverFlowError
和 OutOfMemoryError
。
-
StackOverFlowError
: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。 -
OutOfMemoryError
: Java 虚拟机栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError
异常。 -
Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
扩展:那么方法/函数如何调用?
Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。
Java 方法有两种返回方式:
- return 语句。
- 抛出异常。
不管哪种返回方式都会导致栈帧被弹出。
java栈 stack
本地方法栈 native method stack
栈帧:
方法索引(index)
输入输出参数()
本地变量
CLASS file:引用
父帧:往栈底方向链接下一个
子帧:往栈顶出口链接上一个
2.3 本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError
和 OutOfMemoryError
两种错误。
三种jvm
sun公司 hotpot
bea jrockit
ibm
我们学习都是hotspot
堆 heap
一个jvm只有一个堆,堆内存的大小是可以调节的。
类加载器读取了类文件后,把 类、方法、常量、变量、保存所有引用类型的真实对象
Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常被分为下面三部分:
-
新生代内存(Young Generation)
1.1伊甸园区
1.2幸存区0区 from
1.3幸存区1区 to(默认15次交换后就会晋升到老年区)
-
老生代(Old Generation)
-
永生代(Permanent Generation)8以后称元空间
真理:经过研究,99%的对象都是临时对象
默认情况下,分配的总内存是电脑内存的1/4,而初始化的内存是1/64.
新生区
- 类:诞生和 成长的地方,甚至死亡
- 伊甸园,所有的对象都在此new出来
- 幸存区(0区,1区)
老年区
永久区(持久代)1.8以后叫元空间
这个区域常驻内存的。用来存放jdk自身携带的Class对象。interface元数据,存储的是java运行时的一些环境或者类信息,该区域不存在垃圾回收,关闭vm就会释放这个区域的内存。
如果:一个启动类,加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载,直到oom。
-
jdk1.6之前 :永久代,常量池是在方法区;
-
jdk1.7:永久代,慢慢退化,去永久代,常量池在堆中
-
在JDK8以后,,永久存储区变成元空间,无永久代,常量池在元空间
堆内存调优
GC
gc垃圾回收,主要是在伊甸园区和养老区
假设内存满了,OOM,内存不够outofmemory
手动只能提醒jvm回收,还是靠jvm自动回收
jvm在进行GC时,大部分时候,回收都是新生代~
-
新生代
-
幸存区(form,to )一次交换加一岁,当一个对象经历了15次GC,都还没有死,就会进入老年区
-
老年代
jvm晋升到老年代的动态年龄判断算法?
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代
新的动态年龄判断算法:Survivor区的对象年龄从小到大进行累加,当累加到 X 年龄时的总和大于50%(可以使用-XX:TargetSurvivorRatio=? 来设置保留多少空闲空间,默认值是50),那么比X大的都会晋升到老年代。
轻GC和重GC分别在什么时候发生?
GC两种类:轻GC(普通的GC针对新生代,偶尔幸存区),重GC(全局GC所有的都释放掉)
GC常用算法
标记清除法,标记压缩,复制算法,引用计数法,怎么用的?
引用计数法:
对象用一次 加一次,如果没用过,就会被清除。
引用计数法和可达性分析是判断是否可以回收,剩下的是回收算法。
复制算法:
主要用在新生代(伊甸园、幸存区to、幸存区from)
好处:没有内存的碎片~
坏处:浪费了内存空间,多了一半空间永远是空to。
复制算法最佳使用场景:对象存活度较低的时候;新生区~
标记清除法:
扫描这些对象:对活着对象进行一次标记。
清除:对没有标记的对象,进行清除
优点:不需要额外空间~
缺点:两次扫描,严重浪费时间,会产生内存碎片。
标记压缩法:
优点:对标记清楚法的优化,防止内存碎片,再次扫描,向一端移动存活的对象、
缺点:多了一次移动成本。
gc算法总结:
内存效率(时间复杂度):复制算法》标记清除》标记压缩
内存整齐度:复制算法=标记压缩》标记清除
内存利用率:标记压缩算法=标记清除算法》复制算法
没有最优算法,只有最合适算法:分代收集算法:
年轻代:存活率低,复制算法
老年代:存活率高,标记清除+标记压缩混合实现
JMM:java memory model
1.java内存模型
2.作用:缓存一致性协议,用于定义数据读写的规则
jmm定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory)
多个线程修改同一个变量时,解决共享对象可见性问题:加volatile关键字,修改立即刷新进主内存
jmm 8种指令。
对八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。
java8虚拟机和之前的变化?
什么是oom?项目中出现oom,如何排除?
-Xms1024mb(设置初始化内存分配大小)1/64
-Xmx8096mb (设置最大分配内存),默认1/4
-XX:+PrintGCDetails //打印GC垃圾回收
-XX:+HeapDumpOnOutOfMemoryError //oom DUMP 然后用mat、jprofiler工具查看分析。
- 能够看到代码第几行出错:内存快照分析工具,MAT(Memory Analyzer Tool),jprofiler
- debug,项目代码不多时可以一行行分析
插件MAT,jprofiler作用:
- 分析dump内存文件,快速定位内存泄漏;
- 获取堆中数据;
- 获取内存快照
什么是栈溢出stackOverFlowError?
error和exception的区别?
内存快照如何抓取,怎么分析dump文件?
谈谈jvm中,类加载器你的认识?rt-jar ext application。
项目中遇到的jvm难题,是如何调优的?
**当初在智能问答项目中,我们的程序放在linux服务器测试环境上,运行了一段时间后,项目停止了,查看日志显示Cannot allocate memory,遇到了内存不足的情况,**这时就考虑是不是内存不够的原因了,**服务器上物理内存太小,**大部分都是因为程序太多,内存吃紧,而给jvm分配的内存太大(java程序启动需要的内存,linux不能给),在测试环境下重新调整java程序jvm内存,
解决方案:
1.在linux服务器上利用top命令查看所有进程,看哪些进程占用的内存太大了,选择性的kill,释放内存。
2.调整tomcat中对jvm内存的配置,因为jvm主要内存就是指jvm堆内存。
cd到tomcat的/bin,修改配置文件catalina.sh
**JAVA_OPTS="-server -Xms2048m -Xms16*1024m -XX:+PrintGCDetails //打印GC垃圾回收 - XX:+HeapDumpOnOutOfMemoryError"oom DUMP 然后用mat、jprofiler工具查看分析。**能够看到代码第几行出错:内存快照分析工具,MAT(Memory Analyzer Tool),jprofiler
后面发现是初始化加载词向量的时候没有分页,导致内存报警。
插件MAT,jprofiler作用:
- 分析dump内存文件,快速定位内存泄漏;
- 获取堆中数据;
- 获取内存快照
Java 堆内存开关/JVM的常用调优参数有哪些?
Java提供了大量的内存开关(参数),我们可以用它来设置内存大小和它们的比例。下面是一些常用的开关:
VM 开关 | VM 开关描述 |
---|---|
-Xms | 设置JVM启动时堆的初始化大小。 |
-Xmx | 设置堆最大值。 |
-Xmn | 设置年轻代的空间大小,剩下的为老年代的空间大小。 |
-XX:PermGen | 设置永久代内存的初始化大小。 |
-XX:MaxPermGen | 设置永久代的最大值。 |
-XX:SurvivorRatio | 提供Eden区和survivor区的空间比例。比如,如果年轻代的大小为10m并且VM开关是-XX:SurvivorRatio=2,那么将会保留5m内存给Eden区和每个Survivor区分配2.5m内存。默认比例是8。(幸存区from和幸存区to是一样大小) |
-XX:NewRatio | 提供老年代和年轻代的比例大小。默认值是2。 |
年轻代应该比老年代小。年轻代不宜过大,笼统说确实如此,因为在最坏情况下young gen里可能所有对象都还活着,而如果它们全部都要晋升到old gen的话,那old gen里的剩余空间必须能容纳下这些对象才行,这就需要old gen比young gen大(否则young GC就无法进行,而必须做full GC才能应付了
代的大小为10m并且VM开关是-XX:SurvivorRatio=2,那么将会保留5m内存给Eden区和每个Survivor区分配2.5m内存。默认比例是8。(幸存区from和幸存区to是一样大小) |
| -XX:NewRatio | 提供老年代和年轻代的比例大小。默认值是2。 |
年轻代应该比老年代小。年轻代不宜过大,笼统说确实如此,因为在最坏情况下young gen里可能所有对象都还活着,而如果它们全部都要晋升到old gen的话,那old gen里的剩余空间必须能容纳下这些对象才行,这就需要old gen比young gen大(否则young GC就无法进行,而必须做full GC才能应付了