携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情
1、java虚拟机,JVM内存模型
- 堆中存new出来的对象;
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象,处于物理上不连续的内存空间,只要逻辑连续即可,主要用于存放各种类的实例对象。该区域被所有线程共享,在虚拟机启动时创建,用来存放对象的实例,几乎所有的对象以及数组都在这里分配内存(栈上分配、标量替换优化技术的例外)。
- 每个线程,jvm都会存在分配一块栈内存给它,存放局部变量、操作数栈、动态链接、方法出口
- 其中,堆(Heap)和JVM栈是程序运行的关键,因为:
- 栈是运行时的单位(解决程序的运行问题,即程序如何执行,或者说如何处理数据),而堆是存储的单位(解决的是数据存储的问题,即数据怎么放、放在哪儿)。
- 堆存储的是对象。栈存储的是基本数据类型和堆中对象的引用;(参数传递的值传递和引用传递)
扩展:进程与线程的区别?
做个简单的比喻:进程=火车,线程=车厢
线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
2、JVM 堆内存
堆被划分成两个不同的区域:新生代 ( Young )、老年代 (Old)。
新生代 ( Young ) 又被划分为三个区域:Eden、S0、S1。 这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收
Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。
OOM:内存溢出
解释:full GC 线程回收不了内存空间了,所有的对象都是非垃圾对象;此时再往老年代中放对象,那么就是发生内存溢出!没有空间了,只能内存溢出报错;
垃圾回收线程:
STW:stop the world;停止用户线程;用户感知就是:用户点击调接口的话,突然网站卡顿了一下;
minor GC 只回收年轻代的垃圾对象,会引发STW,但是时间非常短,暂不关注它;
full GC 只回收老年代的垃圾对象,会引发STW,时间比minor GC的时间长,需要重点关注,JVM调优的目的也是减少full GC的发生,最终的目标是减少STW,进而不影响系统性能吞吐量,影响到用户;
面试题:
为什么JVAV虚拟机,要设计STW机制,停止用户线程呢?不设计可以吗?
答:不可以;
垃圾收集过程:假设没有STW机制;当我们程序正在运行过程中,JVM产生GC线程进行垃圾回收。
找垃圾的算法是:可达性分析算法!
找到所有GC ROOT的最底层,全链路的所有对象标记为 非垃圾对象; 则剩下没有标记的,都是垃圾;
因为你整个GC过程中,没有STW;所以用户线程是可以同步继续执行的;
我们假设 你GC线程还未结束,我们的用户线程已经结束了;
用户线程已结束,线程栈的内存空间都会被释放掉;释放掉意味着 局部变量都销毁了;
但是GC刚刚在找的过程中,这些局部变量都是非垃圾的;但是现在怎么又变成垃圾对象了;
我这些对象原本被标记了非垃圾,现在又变成了垃圾对象; 难道我GC又要去找一遍吗?
显然不现实,我堆里有成千上万个对象,我又重头找一次。那我GC永远结束不了了!
所以出于这个原因,java虚拟机让用户线程先停掉,让GC专心把垃圾回收掉后,再恢复用户的线程;这样性能可能还会高一些;