JVM虚拟机

什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

JVM参数

 

JVM内存结构

程序计数器:当前线程所执行的字节码的行号指示器,线程私有,不会发生内存溢出。

虚拟机栈:每个线程运行时所需要的总内存,称为虚拟机栈。每个栈由多个栈帧组成,对应者每次方法调用所占用的内存。每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

存储内容:局部变量表-基本类型和对象引用类型(不同于对象本身),操作数栈,方法出口等信息。

问题1:垃圾回收器是否涉及栈内存?

垃圾回收器不涉及栈内存,方法执行完就会弹出栈,内存就会被回收了。

2.栈内存分配是越大越好么?

-Xss size设置栈内存大小。并不是,栈越大,会使线程的数量越小,因为计算机的物理内存是一定的。一般系统默认的栈内存就可以了。-Xss128k默认

  1. 方法内的局部变量是否是安全的?

如果方法内局部变量没有逃离方法的作用范围,他是线程安全的

如果局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全问题。

  1. 栈内存溢出?
  1. 栈帧数量过多,比如递归调用太深。
  2. 栈帧过大,一般不会出现这种情况。
  1. 线程运行诊断?

top命令查看进程

ps H -eo pid,tid,%cpu 查看具体线程

Jstack 进程id 可以根据线程id找到有问题的线程,进一步定位到有问题代码的源码行号

 

本地方法栈:java调用JNI所需要的内存空间。

(Hotspot将本地方法栈和虚拟机栈合而为一)

 

:通过new关键字,创建对象都会使用堆内存

特点:线程共享,堆中对象都需要考虑线程安全的问题,有垃圾回收机制

 

堆内存溢出问题?

长生命周期对象拥有短生命周期对象的引用就可能导致内存溢出。

 

-Xmx和-Xms控制堆内存大小。

-Xmn设置新生代内存大小。

堆内存诊断

1.jps工具 查看当前系统中有哪些java进程

2.jmap工具 查看堆内存的占用情况(只能查看某一时刻的)

jmap -heap pid 查看整个jvm内存状态

jmap -histo pid 查看jvm堆中对象详细占用情况

Jmap-dump:format=b,file=文件名 pid

 

Eclipse Memory Analyzer 分析jvmduidump文件的插件。

  1. jconsole工具 图形界面的,多功能检测工具(可以连续检测)
  2. Jvisualvm工具 可视化的方式展示虚拟机内容。

方法区内存溢出

-XX:MaxPermSize=8m 方法区的实现是永久代位置在heap上。

-XX:MaxMetaspaceSize=8m java1.8的方法区实现是元空间,位置在本地内存。

 

字节码动态生成技术,动态的完成类加载。Spring和mybatis都会动态加载很多类,容易造成方法区的内存溢出,但是方法区用元空间实现后,存储位置在本地内存,不容易造成内存溢出。

 

class文件结构(类基本信息(访问修饰符,包名类名,版本,父类,接口等),常量池,类方法定义(包含了虚拟机指令)

 

运行时常量池

常量池就是一张表,虚拟机指令根据这张表找到要执行的类名,方法名,参数类型,字面量等信息

运行时常量池,常量池是字节码文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变成真实地址。

S3 == s4:false s3 == s5:true s3 == s6:true

x1 == x2:false

调换后两行位置后:  1.8的是x1 == x2:true,1.6的是x1 == x2:false

 

s3 编译器优化 相当于s3 = “ab”,因为编译的时候就可以确定s3的值

S4采用的是new stringbuilder.append(s1).append(s2).toString 相当于new String(“ab”)

字符串常量池是延迟加载的,即执行一行代码就把字符串常量加入到池中,并不是一次性全部加载。

s.intern()//1.8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池。最后都会把串池中的对象返回。

1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则把此对象复制一份放入串池。最后都会把串池中的对象返回。

 

总结:StringTable(字符串常量池) 特性

常量池中的字符串仅是符号,第一次用到时才变为对象。

利用串池的机制,来避免重复创建字符串对象。

字符串变量拼接的原理是StringBuilder(1.8)

字符串常量拼接的原理是编译期优化

可以使用intern方法,主动将串池中还没有的字符串对象放入串池中。

 

StringTable1.6

Stringtable 在常量池中,并位于permGen永生代(方法区的具体实现)中。

Stringtable1.8

永生代被删除了,使用元空间(MetaSpace)来替代(方法区的具体实现)。元空间位于本地内存中,常量池于位元空间中,但Stringtable单独放到了堆中。

永生代只有当触发full gc时才会进行垃圾回收,但是只有当老年代内存不足时触发full gc。

 

Stringtable垃圾回收(HashTable类似,使用数组和链表实现)

-Xmx10m -XX:PrintStringTableStatistics -XX:printGCDetails -verbose:gc

跟堆的垃圾回收一样。

StringTable调优

  1. -XX:StringTableSize=20000(如果要是程序中字符串常量池中数据比较多,可以把该值给的比较大)这个是hashtable桶的个数,桶的个数越大,哈希冲突的可能就越小,那么查找速度就越快。
  2. 考虑将字符串对象是否入池。使用String.intern()对内存进行调优,将大量重复的字符串对象放入到串池中。

如何判断对象可以回收

 

引用计数法

只有对象被其他变量所引用,那么就让该对象的引用计数+1,不再引用就-1。没有引用就为0,就可以被垃圾回收了。

弊端:循环引用,A对象引用B对象,B对象引用A对象。就会造成对象使用完却无法被回收的现象。

可达性分析法(java使用)

通过可达性分析法来判断对象是否可用。通过一系列称为GC roots的对象作为起始点,从这些节点开始向下搜索,搜过的路径称为引用链。如果一个对象到GCroots没有任何引用链,那么它就是可回收对象。

GC roots对象:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中JNI引用的对象。

四种引用

强引用

new一个对象然后赋值给了变量,那么变量就强引用了这个对象。比如Object obj = new Object(),只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

软引用

SoftReference,在系统将要发生内存溢出异常之前,将这些对象列入回收范围内进行第二次垃圾回收。如果这次回收还没有足够的内存才会抛出内存溢出异常。

弱引用

WeakReference,当垃圾回收器工作时,无论当前内存是否足够,都会回收只被弱引用的对象。

虚引用

PhantomReference,虚引用完全不会对其生存时间造成影响。唯一的作用就是在这个对象被收集器回收时收到一个系统通知。以NIO的ByteBuffer为例,它就是一个虚引用。当ByteBuffer使用完被回收后,虚引用Cleaner对象就会进入到引用队列,ReferenceHandler会定时查找队列里是否有Cleaner对象,如果有就会调用Cleaner对象的clean方法,clean方法就会根据之前记录的直接内存的地址调用Unsafe对象的freeMemory方法来释放掉直接内存。

终结器引用

当一个对象重写了Object的finalize()方法,并且没有强引用时,虚拟机会自动帮忙创建终结器引用。当对象被垃圾回收时,就把终结器引用加入引用队列。再由一个优先级较低的finalizeHandler线程会在某些时间查看引用队列是否有终结器引用,根据终结器引用找到引用的对象,然后调用对象的finalize()方法。调用完后,等下一次垃圾回收时就可以真正回收掉占用的内存。(不推荐使用finalize()方法,效率极低,内存并不会立即释放,可能长时间占用)

软弱虚终结器引用也是对象,可以配合队列使用。虚引用和终结器引用必须配合引用队列使用。如果在创建时分配了一个引用队列,那么当引用对象被回收掉后,引用就会进入引用队列。然后可以把队列里面的引用资源给释放掉。

 

垃圾回收算法

标记清除

优点:速度块

缺点:会产生内存碎片

标记整理

优点:不会产生内存碎片

缺点:整理过程会移动对象的位置,速度较慢

复制

 

  1. 先在from标记不被引用的对象2.然后把from区上存活的对象复制到to区中。3.清空from区,并交换from和to区。

优点:速度块,且不会产生内存碎片

缺点:需要占用两倍的内存空间

垃圾回收器结合了以上的多种垃圾回收算法实现。

 

分代垃圾回收

把java堆分为新生代和老年代。新生代又分为伊甸园,幸存区from和幸存区to。这样可以把不同生命周期的对象放到不同特点的内存中。新生代的垃圾回收较为频繁,老年代的垃圾回收不频繁,适合长时间存活的对象。类似家里面的垃圾一样,有些比较有价值的旧物件也是垃圾但是舍不得丢掉,会放在家里面的储藏室里。家里的垃圾桶放没有价值的新垃圾。垃圾桶经常清理,随着日子的增长储藏室的东西越来越多然后需要清理,然后相当于老年代垃圾清理触发full gc。它的频率低于垃圾桶的回收频率。

工作原理:

当创建新对象时,对象会被分配到伊甸园中。当伊甸园的内存不足时就会触发Minor GC,采用复制算法,将存活的对象复制到幸存区to中,并将对象的寿命+1(对象寿命的阈值最大为15,即对象头中4个bit来存放其信息).然后交换幸存区from和幸存区to的位置。伊甸园和幸存区to的内存就空出来了。第二次除了伊甸园中的对象还会清理幸存区from中国的对象,按照前面的方式回收。当幸存区的对象寿命超过阈值就会把对象放入老年代中。当老年代中内存也不足时,就会触发一次full gc。

相关vm参数

垃圾回收器

7款垃圾收集器

Servial:单线程,复制算法

Servial old配合servial使用,标记整理算法

ParNew:多线程,复制算法(在servial基础上改进成了多线程)(目标提高回收速度,缩短回收时间)

Parallel Scavenge:多线程,复制算法 (目标在于达到一个可控制的吞吐量)

Parallel old:多线程,标记整理算法

CMS:多线程,标记清除算法,总共四个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 标记清除(用户线程继续执行)

CMS缺点:1.对cpu资源敏感。并发标记,虽然不会导致用户停顿,占了一部分线程导致程序变慢,降低吞吐量。

  1. 标记清除的时候还会产生新的垃圾,这些垃圾称为浮动垃圾,cms无法在该次垃圾清理过程中清除这些垃圾。
  2. 基于标记清除算法,会产生内存碎片。

 

G1:多线程,整体是标记整理算法,局部是复制算法,总共四个步骤:

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收(用户线程不能继续执行)

特点:

  1. 能够充分利用多cpu,多线程环境的硬件优势,尽量缩短stw(暂停其他线程)时间。
  2. G1整体采用标记整理算法,局部是复制算法。
  3. 宏观上G1不再区分年轻代和老年代,内存被划分为多个独立的region区域。但小范围内还是有年轻代和老年代的区分,但是不是物理隔离,而是一部分region的集合,不需要内存连续。即还是会采用不同的gc方式来处理不同的区域。G1的年轻代和老年代是逻辑概念,每个region会随着G1的运行在不同代之间进行切换。
  4. 筛选回收会对各个region的回收价值和成本进行排序,根据用户所期望的GC停顿来制定回收计划,优先回收成本小,价值高的region区域。

与CMS的相比的优势:

没有内存碎片,可以精确控制停顿。

 

新生代GC(minor GC)指发生在新生代的垃圾收集动作,特点:收集速度快,频率也快。

老年代GC(major GC/Full GC)指发生在老年代的垃圾收集动作,出现full gc通常会伴随着至少一次的minorGC。Full GC的速度一般比Minor GC慢10倍以上。

内存分配策略:上面的是一些垃圾回收的策略,以下是内存分配策略

1.对象优先在Eden分配

对象在新生代Eden区中分配。当Eden区没有足够空间时,虚拟机将发起一次minor gc。

2.大对象直接进入老年代。

大对象指需要大量连续内存空间的java对象,比如很长的字符串已经数据。

3.长期存活的对象进入老年代。

对象头中存放了对象的年龄,对象在Eden中诞生并经过一次minor 后仍然存活,并且能够被Survivor区容纳的话,就会进入Survivor,并且对象年龄设为1.然后每熬过一次minor gc后年龄就+1,当年龄到达一定程度后(阈值默认为15)就会晋升到老年代中。

4.动态对象年龄判断。

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivir空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

对象的内存布局

对象在内存中存储的布局可以分为三块区域:对象头,实例数据和对齐填充。

64位计算机的对象头,markword是8个字节。Class对象指针是四个字节

 

类加载机制

1.加载

1.通过类的全限定名得到该类的二进制字节码文件

2.将字节码文件中的静态数据结构转换成运行时数据结构

3.在内存中生成该类的class对象

2.链接

1.验证

1.文件格式验证

2.元数据验证

3.字节码验证

4.符号引用验证

2.准备

为静态属性分配内存,并初始化为默认值。

3.解析

将符号引用转换成直接引用

3.初始化

初始化类,静态属性和静态代码块初始化。

 

类加载的时机(一共6种)?

 

双亲委派机制?什么作用?怎么破坏?写一个String类能加载进去么?

当某个类加载器加载某个.class文件的时候,并不是由该类加载器加载,而是由这个类加载器的上级类加载器加载,递归操作,如果上级的类加载器没有加载,自己才会加载这个类。

作用:1.避免类的重复加载。2.避免核心类被篡改。

破坏:

1.自定义类加载器,重写loadClass方法;

2.使用线程上下文类加载器;

不能。

BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。

ExtensionClassLoader(扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。

AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。

JVM类加载方式

类加载有三种方式:

  • 1、命令行启动应用时候由JVM初始化加载
  • 2、通过Class.forName()方法动态加载
  • 3、通过ClassLoader.loadClass()方法动态加载

Class.forName()和ClassLoader.loadClass()区别

  • Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
  • ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
  • Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值