JVM简介

JVM包含:java栈、堆、本地方法栈、程序计数器、方法区

堆:它是jvm所管理的区域中占有内存最大的一块区域,大部分的GC都发生在堆上,基本上所有的对象的实例都在其中分配内存(并不是所有https://blog.csdn.net/w372426096/article/details/80333657,如果不发生逃逸的话,可以开启逃逸分析配置,将不逃逸的对象内存分配由堆上变为栈上)
java栈:java方法执行时会将一个栈帧压入栈中,方法的执行过程就是栈帧的入栈出栈过程,栈帧中存储了方法中使用的局部变量(基本数据类型、对象的引用等)、中间计算结果、方法返回出口信息等
本地方法栈:与Java栈类似,不同的是Java栈为java方法服务,而本地方法栈为native方法服务
程序计数器:java文件是被编译成字节码执行的,它用于指明当前执行到了哪一行
方法区:方法区用于存储已经被加载的类信息、方法信息、静态变量、常量等数据,常量池也属于方法区
其中堆、方法区为线程共享,当虚拟机启动时创建,而java栈、本地方法栈、程序计数器为线程私有,它的生命周期与线程的相同

JVM中对象的访问定位方式:https://blog.csdn.net/liupeifeng3514/article/details/79111651
在java栈中有reference数据,它是对象的引用,jvm利用它来定位堆中的对象,包含以下两种方式:
(1)reference指向的是java堆中的句柄池中的某一句柄,该句柄指向了堆中对象内存的位置和方法区中对象的类型信息
(2)使用直接指针,reference指向的直接是堆中对象的内存地址,对象内存地址中存储了该对象的类型信息在方法区中的位置
这两种方式各有特点,使用句柄的好处是当对象的位置发生改变时(垃圾回收时对象位置会发生变动),是句柄指向的地址信息会发生改变,而reference指向的地址不会改变;而直接指向对象地址的好处就是速度更快,因为减少了一次位置定位的过程,JVM中就是使用了这种方式

JVM中栈、堆、方法区调用过程:https://blog.csdn.net/heart_mine/article/details/79497209
AppMain.java
public class AppMain {
  public static void main() {
    Test test1 = new Test();
    test1.testMethod();
  }
}
执行命令java AppMain,编译Java文件,生成AppMain和Test类字节码文件,启动一个jvm进程,加载AppMain.class字节码文件,将类信息存储进方法区中,然后定位到方法区中main方法,开始执行,首先在java栈中找test1引用,发现并没有,这个时候Test类的对象还没有被创建。然后jvm会加载Test()类,将Test类信息、方法信息存储进方法区中,接着创建Test对象,在堆中分配内存,接着创建一个Test对象的引用test1,将它放在main方法线程栈中,创建工作就完成了。执行test1.testMethod();语句时,会先在栈中找打test1,根据该引用定位Test对象在堆中的位置,堆中会存储该对象类信息和方法信息在方法区中的位置,找到testMethod()方法的信息,并执行该方法。

JVM垃圾回收:who(谁需要被回收) how(怎么样回收) when(什么时候回收) https://www.cnblogs.com/1024Community/p/honery.html#22-%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90%E7%AE%97%E6%B3%95
who
哪些内存需要回收:在jvm分区中java栈、本地方法栈、程序计数器是线程私有的,随线程创建而创建,随线程死亡而回收不用太多的考虑回收的问题,GC主要发生在堆和方法区中。
方法区如何判断是否需要回收?
方法区中回收主要包含无用常量、无用的类信息,如果常量没有再被引用时就可以被回收,类信息则是对应类的实例和类加载器已经被全部回收。
如何判断堆中一个对象是否可以被回收?
(1)引用记数法:每个对象都有一个变量,当对象被创建时后,将其地址分配给某一引用时,该变量初始为1,每当有其他引用也指向该对象内存地址,则变量加1,反之减1,当该变量为0时,说明没有引用再指向它了,可以被回收。优点:简单易操作,缺点:会有对象间循环引用的缺陷
(2)可达性分析:从一个根节点GC ROOT点开始寻找它所引用的对象,找到这个对象后继续以该对象为新的节点寻找它所引用的对象,直到找到所有叶子节点。而其他没有被包含在其中的节点则为可回收节点。
GC ROOT节点的选取可以从栈中引用、方法区中静态对象的引用、方法区中常量对象的引用中选取,通过一系列GC ROOT来标记不可回收对象。

how
常见的GC算法有哪些?
标记-清除算法
在可达性分析中,通过对一系列GC ROOT搜寻其引用,对这些存活的对象都添加一个存活标记,当搜索完成后,对那些没有标记的对象,统一的从内存中进行回收清理,由于该种方式是直接回收对象内存,没有进行移动因此会造成内存碎片
标记-整理算法
与标记清除法类似,只是在进行未标记对象内存回收后,会将存活的对象向左端移动,并更新它们的引用地址,解决了内存碎片问题。
分代回收算法
现在大部分虚拟机使用该方式,将堆区分为新生代、老年代,在堆外还有一个持久代,在新生代中每次回收都会有大量的对象被回收,老年代中对象被回收较少,持久代中基本不被回收。
新生代中按照8:1:1分为1个eden区2个survivor区,假设一个是s1,一个为s2,s1,s2时刻保持一个为空,对象刚生成时会被分配在eden区,当某个对象到来发现eden区空间不足时会发生minor GC,将eden区中存活对象复制到到其中一个survivor 假设为S1,然后将eden清空,新对象到来时重新在eden进行空间分配,eden空间不足时会再次进行回收,这时eden和S1都会进行Gc,将其中的存活对象复制到S2,并将eden和s1清空,之后重复这一过程。每进行一次GC存活对象的年龄都会加1,到8之后就会转入老年代,即如果一个对象经过多次垃圾回收依然存活,它也会被放入老年代。年轻代发生的GC叫做minor GC
老年代:当老年代满了以后会发生full GC,此时年轻代、老年代都发生GC
持久代:hotspot持久代一般指的是方法区,jdk 1.8后,方法区被元空间所代替

为什么年轻代要有两个survivor?

如果没有survivor,eden每进行一次垃圾回收,存活的对象就会进入老年代,这样很快老年代就会装满,当进行老年代major Gc时,也会触发年轻代的Gc,它的耗时相对会比较长,会比较频繁的触发full gc;如果只有一个survivor,当eden空间不足,eden和survivor都进行gc时,虽然eden可以将存活对象复制到survivor,但是survivor会存在大量的磁盘碎片,如果想要避免这种情况还要进行整理,也会有较大性能消耗。

when
什么时候会发生垃圾回收?
GC根据分类可以分为年轻代的GC和full GC
年轻代GC:当eden区满了以后就会发生
full GC:老年代满了,持久代满了、主动调用system.gc()

字符串常量池在堆中,存储的是字符串的引用,字符串存储在堆的其他区域中,String.intern()方法,若常量池中没有指向该字符串的引用,则将指向该字符串的引用放在字符串常量池中并返回,如果有的话则直接返回该引用
String s1 = new String("1")+new String("2");
s1.intern();//将“12”的引用放在字符串常量池中
String s2 = "12”;//获取“12”引用
System.out.println(s1==s2);
在jdk8它的返回是ture,若将s1.intern()注释掉,则返回false

JVM监控

JConsole 内存查看工具

Jvisualvm 查看内存cpu使用情况

Eclipse mat 内存分析工具,查看java用户线程、类实例的总数、大对象、支配树、内存泄漏的可能原因等

Jps 查看当前java进程

Jstat 查看堆中内存年轻代、年老代的使用情况,gc的次数,可以用于监控虚拟机运行时各类统计信息

Jmap [-heap]查看堆中对象的情况,[-dump]生成快照文件

jhat 用于分析jmap生成的快照文件

Jstack 可以输出java进程中各线程的运行情况

项目中使用了zabbix+grafana进行jvm监控

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值