1. Javaのメモリ領域
データ領域の1.1。JVMランタイムのメモリレイアウト
1.2。ヒープヒープ
-
その唯一の目的は、オブジェクト・インスタンスを格納することで、ほぼすべてのオブジェクトインスタンスおよびアレイは、メモリ領域が割り当てられます。
-
スレッドのヒープメモリ領域は、並行プログラミングでスレッドの安全性の問題を考慮する必要共有領域です。
-
-Xms256M -Xmx1024M
ヒープメモリサイズ。注:Javaプログラムは、ヒープ領域の拡張を実行しており、減少し続けるだろう、それはシステム圧力の原因となります、それは一般的に同じ大きさに設定されています
-X
:動作パラメータを示しますms
:サイズを開始メモリ開始、すなわちを示しますmx
:メモリの最大を示し、すなわち最大メモリ -
ヒープが分かれ:新世代と旧新誕生で、このようなオブジェクトの名前として、二つのブロック、例外が1つありである新世代が大きなオブジェクトは、古い年に作成されます受け入れることができません
-
新世代:オブジェクトは主にされ、分散エデン新生代エリア
それはディストリビューションの新しい世代に失敗し、任意のラージオブジェクトは、オブジェクト参照の配列ですが含まれていない場合は、古い時代に直接割り当てることができます。
-
あなたは、大きなオブジェクト歳のしきい値分布を設定することができます。
-XX:PretenureSizeThreshold
デフォルトは0で、任意のオブジェクトは、新しい世代のためにメモリを割り当てることを意味しますが有効になりませんです。
-
あなたは、新しい世代領域のサイズ-Xmn256M 256Mによって提供されてもよいです。ここでサイズ(エデン+ 2サバイバースペース)、
-
-XX:ServivorRatio=8
メモリ空間サバイバー8にエデン決め占め:1 -
オブジェクトの長期生存率は、古い年を入力します:年齢> =が15歳に昇格する場合は、各仮想マシンは、プライマリ・オブジェクトの年齢を設定します。
YGCが生き残る後のオブジェクトは、エデンに表示されたら、年齢のこの時点でServivor領域に移動されるものとなります。各YGC後、ライブオブジェクトの年齢が1になります。それが回復したりプロモーション歳まで。
YGCはさらに、移動するオブジェクトが、直接、古い年に、サバイバーの最大容量よりも大きい場合。
-
あなたは年齢のしきい値を設定することができます。
-XX:MaxTenuringThreshold
年齢は古い時代にこの値に達するとき。年齢は到達しなければならない対象ではありません
MaxTenuringThreshold
サバイバーで同じ年齢の合計がすべてのスペースサバイバーの半分よりも大きいサイズ、より年齢の大きいオブジェクトや年齢は古い時代に直接行くことができることをオブジェクトに等しい場合には、旧昇進の前に。 -
ヒープ
OutOfMemoryRrror
(OOMと呼ばれる)新生児の場合は、オブジェクトまたはオブジェクトがOOMプロモーション、配布エリアにスローされます収まりません。十分でない場合は、新生児対象はエデン、エデンに割り当てられると、それはマイナーGCをトリガします。
プロモーションJVMの時にオブジェクトがメモリ不足を発見した場合はサバイバーの下に置くことができない領域、またはしきい値が大きな物体が上限値を超えた場合、古い年を割り当てることができない場合は、古い時代に割り当てようと、トリガーのフルガベージコレクション(FGC )、あなたはまだ下に置くことができない場合は、スローOOM。
我々が使用することができますOOMを分析するには
-XX:+HeapDumpOnOutOfMemory
、JVMがOOM情報を印刷してみましょう。
1.2メソッドのエリア方式エリア(PermGen&メタスペース)
-
JITコンパイルされたコードデータの後にメタタイプ情報フィールド、静的プロパティ、メソッド、定数、等:主記憶領域のための方法。
それぞれ永久領域(PerGen)とメタスペース(メタスペース)と特定方法。
-
ホットスポットPermGenは、永久的な世代と呼ばれる領域に(<= JDK1.7)ユニークです。
この領域でも、ダイナミッククラスローディング場合、傾向OOMパーマ。
java.lang.OutOfMemory: PermGen space
エラーが発生しました。上記のエラーが設定することができます
-XX:PermSize=1024M
解決するには。また、設定することができます
-XX:MaxPermSize=1024m
永久世代の最大サイズを。デフォルトは64MですしかしJDK8とHotSpotのそれ以降のバージョンのようPermGenスペースとドルを交換するため、JDK8以降は、プロンプトが表示されます:Javaのホットスポット64 - サーバーのVMオプションのMaxPermSize = 1024Mを無視して、警告、サポートは8.0で削除されました。
-
メタスペースが新しい永久欠陥バンドを解決し、設計を最適化するためになされ、それがローカルメモリにメモリを割り当て、そしてそれは、ヒープメモリを移動し、すべてのパーマの前の文字列定数です。他のクラスは、メタ情報フィールド、静的プロパティ、メソッド、および他の定数は、メタスペースを移動します。実際には、バージョン1.7には、定数文字列のヒープメモリに移動されました。
ローカルメモリのほとんどのクラスのメタデータの割り当て。「klasses」の記述メタデータが削除されました。デフォルトでは、ローカルクラスのメタデータのみ使用可能なメモリの制限によって。することにより
-XX:MaxDirectMemorySize=50m
、ダイレクトメモリ設定。それはあなたがメモリリークをプログラムしている場合は、ローカルメモリに格納されるため、メタスペーススペースが少ないマシンのメモリにつながる展開を停止し、それはまだ必要なデバッグおよび監視を持っています。
-
メタスペース
-XX:MetaspaceSize=10m
と-XX:MaxMetaspaceSize=50m
初期最大のスペースとスペースを設定
1.3。仮想マシンのスタックJVMスタック
-
スタックは、データ構造の後に進んでいます。JVMは、Javaスタックにスレッドプライベートで実行メモリ領域の方法について説明します。開始から終了までの各メソッド呼び出しは、スタックにスタックからスタックフレームを呼び出した結果です。
-
アクティブなスレッドは、スタックのスタックフレームの上部のみが現在のスタックフレームと呼ばれ、有効です。メソッドが実行されている現在の方法と呼ばれ、スタックフレームは、操作方法の基本的な構造です。ランタイム実行エンジンでは、すべての命令は、現在のスタックフレームを操作するためのものです。
-
ローカル変数テーブル、スタック操作、ダイナミックリンクを格納するためのスタックフレーム(スタックフレーム)、この方法は、アドレスおよび他の情報を返します。
局部变量表:存放方法参数,编译期可知的基本数据类型、对象引用类型(reference)和returnAddress类型(指向一条字节码指令地址)。局部变量表所需的内存空间是在编译期确定,方法在局部变量表中分配多少空间是完全确定的。在运行期间不会改变局部变量表的大小。局部变量没有准备阶段,必须显示初始化。
操作栈是一个初始状态为空的桶式结构栈。方法执行过程中,会有各种指令往栈写入和提取信息。JVM的执行引擎就是基于操作栈的执行引擎。
动态连接: 在Class文件中的常量持中存有大量的符号引用。字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分在类的加载阶段或第一次使用的时候就转化为了直接引用,称为静态链接。而相反的,另一部分在运行期间转化为直接引用,就称为动态链接。
方法返回地址:方法执行时有两种退出情况:一是正常退出,正常执行到方法的返回字节码指令;二是异常退出。两种退出都会返回当前被调用的位置。方法退出相当于弹出当前栈帧,退出的方式有三种:
1.
返回值压入上层调用栈帧。2.
异常信息抛给能够处理的栈帧。3.
PC计数器指向方法调用后的下一条指令。 -
StackOverflowError
:当栈深度超过虚拟机分配给线程的栈大小时就会出现此error。最常见的就是递归深度超出了限定,然后抛出这个错误
-
OutOfMemoryError
:虚拟机扩展时无法申请到足够的内存空间,多线程下的内存溢出,与栈空间是否足够大并不存在任何联系。为每个线程的栈分配的内存越大(参数
-Xss
),那么可以建立的线程数量就越少,建立线程时就越容易把剩下的内存耗尽,越容易内存溢出。 -
可以通过
-Xss2m
设置栈内存大小,设置每个线程的栈内存,默认1M,一般来说是不需要改的。-XX:ThreadStackSize
线程堆栈大小如果把
-Xss
或者-XX:ThreadStackSize
设为0,就是使用“系统默认值”。而在Linux x64上HotSpot VM给Java栈定义的“系统默认”大小也是1MB。JDK1.6以前,谁设置在后面,谁就生效;JDK1.6以后,
-Xss
设置在后面,则以-Xss
为准,-XXThreadStackSize
设置在后面,则主线程以-Xss
为准,其它线程以-XX:ThreadStackSize
为准。
1.4. 本地方法栈 Native Method Stacks
-
本地方法栈为Native方法服务
-
本地方法通过JNI(Java Native Interface)来访问虚拟机运行时的数据区,甚至是调用寄存器,具有和JVM相同的能力和权限。
-
本地方法栈也会抛出:OutOfMemoryError和StackOverflowError
-
JNI
JNI深度使用操作系统的特性功能。复用非Java代码。如果大量使用其他语言来实现JNI,会失去跨平台特性。
如果对执行效率要求高,偏底层的跨进程的操作等,可以考虑设计为JNI调用方式。
1.5. 程序计数器 Program Counter Register
- 每个线程创建后都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都依赖程序计数器。
- 程序计数器是线程独占,在各个线程直接互不影响,在此区域也不会有内存溢出异常。
- 线程如果在执行一个Java方法则记录虚拟机字节码指令的地址,如果代码执行到了Native方法计数器就为undefined。
1.6. 直接内存 Direct Memory
-
直接内存,即本机使用的堆外的系统内存。该部分内存可被JVM使用,不会被JVM堆内存限制,但是动态拓展时也会出现
OutOfMemory
,可用-XX:MaxDirectMemorySize=50m
来限制使用内存空间的最大值最大值 -
DirectByteBuffer
可以直接操作DirectMemory,它通过JNI调用native方法直接分配堆外内存,通过DirectByteBuffer
对象对这块内存对象进行操作这个调用,实际上是从系统的用户态切换到了内核态使用系统调用来完成这个操作。
为什么要切换到内核态?用户态没有权限去操作内核态的资源,它只能通过系统调用外完成用户态到内核态的切换,然后在完成相关操作后再有内核态切换回用户态。
DirectByteBuffer
该类本身还是位于Java内存模型的堆中。堆内内存是JVM可以直接管控、操纵。由于
DirectByteBuffer
的权限修饰符是空的也就是默认的,所以在我们编程中是无法直接new,只允许同包创建,我们可以通过ByteBuffer
中的静态方法allocateDirect(int)
方法来创建对象。public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
而 DirectByteBuffer 类中调用了native的unsafe.allocateMemory(size)来分配空间,实际上是使用了c语言的malloc方法。
// Primary constructor // DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { // 这里是重点!!!掉黑板 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } // 这里记录分配空间的信息。 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; } // 记录分分配空间信息的类 private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; }
2. 对象创建与内存分配
2.1 对象创建
- 对象使用new创建的简单过程
-
指针碰撞:
假设Java堆中内存是绝对规整的,所有用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作 为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump The Pointer)
【带Compact过程的Serial、ParNew等采用指针碰撞。】 -
空闲列表
如果Java堆中的内存并不是规整的,已使用的和空闲的内存相互交错,就无法进行指针碰撞了,JVM就必须维护一个列表,记录可用内存区域,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)
【CMS这种基于Mark-Sweep算法的使用空闲列表】 -
不难想到,分配内存时如果多个线程同时创建对象,就会出现并发问题。JVM实际采用:
一种是CAS(Compare And Swap)加上失败重试机制来保证更新操作的原子性;
另一种是本地线程缓冲(TLAB,Thread Local Allocation Buffer.),即把内存分配的动作按照线程划分在不同的空间之中进行,每个线程都预先分配一小块内存。线程在自己的TLAB中分配,只有TLAB用完才需要同步加锁。虚拟机是否用TLAB,可以通过
-XX:+/-UseTLAB
参数设定。
2.2 对象内存
-
对象头(Header)包含两部分:一是自身运行时数据;二是类型指针
运行时数据: 32位和64位JVM分别对应32位和64位长度(未开启指正压缩),存储包括:哈希码、GC分带年龄、锁状态标志、线程池持有锁、偏向锁ID、偏向时间戳等。(Mark Word)。
类型指针: 即对象指向它的类元数据的指针,虚拟机通过这个指针确定是哪个对象的实例。查找对象的元数据信息不一定要经过对象本身。对象是Java数组,则对象头中则会有一块记录数组长度的数据;普通Java类可以通过元数据信息确定Java类大小,但数组还需要需要对象头中的长度数据才能确定。
-
实例数据(Instance Data)
就是对象存储的真正的有效信息,也就是程序代码中定义的所有字段内容。
-
对齐填充(Padding)
因为HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,对象头部分正好是8字节的倍数,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
2.3 对象访问
-
Javaは、スタック上の基準の特定のデータによってヒープ上のオブジェクトを操作する、および参照は、オブジェクトへの参照であり、参照することによりオブジェクトを検索し、アクセスするために、2つの方法の現在主流:まず、ハンドルを使用して、第二のダイレクトポインタを使用することです
ハンドル:意志具体的に分割されたプール・ハンドルとしてJVMヒープメモリ、ハンドルは、オブジェクトの参照アドレスに格納され、ハンドルは、各オブジェクトインスタンスデータ及びタイプデータの特定のアドレスを含みます。
直接ポインタ:それは直接のポインタである場合は、Javaヒープは、情報の種類に関連するデータへのアクセスを防止します。参照は、直接対象アドレスに格納されています。