标签:field java stat add 内存 实现 年龄 c程序 exception
如上图,可以很清楚的了解到JDK和JRE的关系了。JVM+Lib=JRE 我们用java语言调用java API来编写java程序,通过JDK的javac指令将java文件编译为.class字节码文件给jvm执行,JVM解析字节码,映射到CPU或者OS的指令集被最终执行
操作系统最终执行的当然还是机器吗了。只不过linux系统和window系统会有不同的jre来做中间件,提供java代码的运行环境。这样一来,java代码就可以实现一次编写,处处执行。
我自己写了一个类的例子Math
package com.lyb.jvm; public class Math { private static Computer computer = new Computer(); private static int initData = 666; public int calculate() { int a = true; int b = true; int c = 30; return c; } public static void main(String[] args) { Math math = new Math(); math.calculate(); (new Thread()).start(); } }
编译后找到Math.class文件,然后打开开一眼。就是下面这样的。
读不懂,不过JDK提供了一些指令,比如javap 就可以将字节码文件反编译为比较容易读懂的代码,然后我在IDEA的终端试了一下
Compiled from "Math.java" public class com.lyb.jvm.Math { public com.lyb.jvm.Math(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public int calculate(); Code: 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: bipush 30 6: istore_3 7: iload_3 8: ireturn public static void main(java.lang.String[]); Code: 0: new #2 // class com/lyb/jvm/Math 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method calculate:()I 12: pop 13: new #5 // class java/lang/Thread 16: dup 17: invokespecial #6 // Method java/lang/Thread."<init>":()V 20: invokevirtual #7 // Method java/lang/Thread.start:()V 23: return static {}; Code: 0: new #8 // class com/lyb/jvm/Computer 3: dup 4: invokespecial #9 // Method com/lyb/jvm/Computer."<init>":()V 7: putstatic #10 // Field computer:Lcom/lyb/jvm/Computer; 10: sipush 666 13: putstatic #11 // Field initData:I 16: return }
第二种就简单多了,然后我在网上找了一份对照代码表。https://blog.csdn.net/wangfantastic/article/details/79736860
一个java程序是用java Math.class这个指令通过类装载子系统装载到JVM的内存中并被字节码执行引擎执行的的过程如下
栈是用来存放程序运行过程的局部变量的。如Math这个类对象在执行的时候,首先执行main方法,其中有一个局部变量math,并且main方法中调用的calculate这个方法中又有a,b,c这三个局部变量。如果都放在同一个区域当然是分不清的,所以不同的方法会再划分出不同的块来存放各自的局部变量。这个块就是栈帧
main方法一旦执行会启动一个主线程,此时JVM的内存中就会在 栈(线程)部分为主线程划分一个内存区域。同样的,如果有第二个线程,会再从栈中划分出一个内存区域给这个线程。每一个线程的栈模型如下图
栈当然是满足FILO原则的,main方法先执行,需要他的局部变量,划分一个区域main先进,calculate后进,当calculate执行完了,局部变量当方法执行完,是要全部销毁的,所以calculate的栈帧先出栈。这也就是为什么java在设计的时候要选择栈作为内存结构中的局部变量存放区域,因为这和java代码中后调用的方法反而先执行完是吻合的。
栈帧当然不是简单的就存储局部变量,她还要分为:局部变量表,操作数栈,动态连接,方法出口
局部变量表可以理解为方法中定义的局部变量,操作数表就是说一些中间数据,比如int a=1;这里的1就是一个操作数而a是局部变量。方法出口相当于一个标记,比如main方法调用caculate方法,当calculate方法执行结束,知道回去main方法中的那个位置继续执行。这就和程序技术器类似,程序计数器是针对每一个线程的,每一个线程都有一个程序计数器,在运行的时候如果线程挂起了,会记录当前线程执行的代码所在的行,这样当线程再次被唤醒的时候可以接着执行,而不是重新执行。程序计数器是由字节码执行引擎来维护的。局部变量当然不仅仅是简单的数据类型,可能还是一个对象,比如math对象,那这个局部变量表中存放的其实是math对象的地址指针,真正new出来的对象是放在堆内存中的。所以栈和堆之间有一个栈指向堆的关系。
一个字节码文件被类加载器加载后其实是放在JVM内存模型的具体是方法区中的,方法区主要存放的是类相关的信息,比如静态变量,常量(static final),还有就是元数据,类的元数据包含很多,比如这个类的名字,修饰符,方法名字,修饰符,方法里面的指令等等,可以说构成类的信息都会存放在元数据中。方法区在java8之后是直接使用物理内存了。
如果一个静态变量=new User();这样,静态变量放在方法区,而new的对象放在堆中。所以方法区中放的其实就是该对象的指针地址。而对象和数据元之间也并不是单向的关系,对象在创建的时候,会将他的元信息保存在对象头当中。对象并不是简单的属性和方法构成的,对象的数据模型如下图
看到这里我好像明白了什么,对象头中有包括元数据的指针也就是类型指针。所以说方法区和堆之间是一个双向关联的关系。
本地方法栈就是说95年在java出现的时候,大家发现了java的优势,很多新的程序使用了java开发,但是与之前的c程序之间并没有很好的对接。当需要调用C语言的.dll文件(类似jar包)的时候就会执行一些native的方法。这些方法的变量就是存放在这个本地方法栈中的。
就像前面讲到的,一个线程会分配一个栈,一个方法会分配一个栈帧来存放局部变量,如果一个局部变量是引用类型的,就会在堆中有一个对象。当方法执行结束的时候,局部变量被销毁,就不再有什么指向这个局部变量了。其他的程序也基本很难使用这个对象了,此时这个对象就是需要被GC的对象。也可以用可达性算法来判断对象是否可以被销毁。
可达性算法:在上面的分析可以知道,栈中的局部变量和方法区的静态变量,本地方法栈中的局部变量都是可能指向堆的,这些可以指向的出发点就是GC Root,这一系列根出发一直向下找的对象形成一个网络。在这个网络中的对象就是可达对象,否则是不可达对象,不可达对象久要被销毁。
如上图,minor gc是由字节码执行器引擎来触发的,堆内存又分为年轻带和老年代。年轻带中的Eden区放满了之后就会进行一次gc,一次gc之后的对象会被放到From区,且每次gc之后的对象复制会将对象头中的对象分代年龄+1 。当Eden区又满了再gc一次,此时会gcEden区和From区,gc后的对象全部放到To区并年龄+1。再过一段时间Eden区又满了gc一次,清理Eden和To区,gc后存活的对象放到From区。也就是说From区和To区中总又一个是空的,用来存放下一次GC之后的所有对象。达成一个循环。当对象的年龄java默认为15时,判定这些对象是老不死的对象,要一直被拷贝移动。耗费资源和空间,然后就会被移到老年代中。
使用java自己的jvisualvm命令启动GC监视器,来查看垃圾回收的过程,最后这个Tab需要自己在网上下载安装也很简单
我是参考https://blog.csdn.net/u012988901/article/details/102517829进行安装的,然后我写了一个死循环,准备作死运行一波看看效果,代码如下,好了,我要开始运行了
package com.lyb.jvm; import java.util.ArrayList; import java.util.List; public class NeverStop { byte[] b=new byte[1024]; public static void main(String[] args) throws InterruptedException { List list=new ArrayList();//list中会引用每次新建的NeverStop类的对象,这样的话就不会轻易被gc,而是会满了才gc,但是都有引用,又gc不了,预测会内存溢出 while(true){ list.add(new NeverStop()); Thread.sleep(10); } } }
开始了,下面是刚开始运行我调用到java visualvm的过程监视到的情况,可以看到,已经进行几次gc了,老年代中已经有一些对象了,但是好像程序还很坚挺
a few minutes later,老年代基本也快满了。。。疲软中...
果然,最后到IDEA中,发现程序已经OVER了, 这个提示也是非常的OK直接说是堆空间
对于以上的情况面对非常高并发,大数据量访问的情况也是要进行JVM调优的,下次收集一些JVM调优的资料,学习后发出来分享。下面只给出一个思路
可见总有一天老年代也会放满,这时候字节码执行器引擎就会出发full gc,父GC,会堆整个堆内存进行垃圾回收,其实每当gc,不管是minor或者是full的时候都会暂停一些执行中的线程,而执行垃圾回收的线程。这个时候程序可能出现卡顿现象,严重的时候会发生STW,也就是STOP THE WORLD...... 所以JVM调优主要就是两个方向:A.减少gc的次数 B.减少每次gc的时间
标签:field java stat add 内存 实现 年龄 c程序 exception
原文地址:https://www.cnblogs.com/liuyongbo/p/11980326.html