來人啊給我炸了那個Java虛擬機(jī)
你指尖躍動的電光,是我此生不滅的信仰,唯我超電磁炮永世長存。
瞬間爆炸,完成單殺。
深度長文,非常非常長,執(zhí)行這些程序可能導(dǎo)致機(jī)器完全死機(jī),請遵照指示安全開車。
JVM中分了兩大塊,公共區(qū)域和棧私有區(qū)域。公共區(qū)域中有堆,用來放對象的。還有方法區(qū),用來放一些類信息啊,方法信息啊或者一些運(yùn)行時的常量信息的。棧私有區(qū)域中有分為,PC寄存器(下一條操作指令地址),棧(臨時的指針和數(shù)值)和本地方法區(qū)(native方法調(diào)用會用到)。
今天教大家怎么花式搞死Java虛擬機(jī),順便大概知道一下GC是啥,先了解一下JVM內(nèi)存的結(jié)構(gòu)吧。
真實(shí)的GC信息是長這樣的。
- PSYoungGen total 3072K, used 128K
- eden space 2560K, 5% used
- survivor space
- from space 512K, 0% used
- to space 512K, 0% used
- ParOldGen total 6656K, used 408K
- object space 6656K, 6% used
- PSPermGen total 4096K, used 3039K
- object space 4096K, 74% used
一般的GC過程都是這樣的,***產(chǎn)生的對象,是可能***就要消滅嘛~對象先在Eden區(qū)出生,過一段時間GC掃描,如果對象還能用,那就丟到Survivor區(qū)。如果再過一段時間還能用,那就繼續(xù)丟到OldGen區(qū)。PerGem區(qū)呢,只會放一些Class類啊,方法啊,1.7之前字符串常量池也是放這里,只有Full GC的時候會進(jìn)行回收。
有小伙伴就會問了,那為毛Survivor有兩個區(qū),from和to?這是其中一個GC策略,每次GC在對Survivor區(qū)掃描的時候呢,會把有用的從from 直接 復(fù)制到to區(qū),這兩個區(qū)是互相備份的,這樣就減少了內(nèi)存碎片的信息收集了,這樣from-to-from-to來回來回好幾次,才把他們丟到老年代。
好了,開始花式吊打JVM了,先指定一下我們今天的JVM配置,大家自己配上,啊。
- -Xmx10m
- -XX:MaxPermSize=5m
- -XX:MaxDirectMemorySize=5m
- -XX:+PrintGCDetails
首先咱的主類長這樣。
- public class BlowUpJVM {
- }
既然說了是花式,今天的過程是這樣的。
- - [√] 棧深度溢出
- - [ ] ***代內(nèi)存溢出
- - [ ] 本地方法棧溢出
- - [ ] JVM棧內(nèi)存溢出
- - [ ] 堆溢出
- public static void testStackOverFlow(){
- BlowUpJVM.testStackOverFlow();
- }
棧不斷遞歸,而且沒有處理,所以虛擬機(jī)棧就不斷深入不斷深入,棧深度就這樣爆炸了。
- - [ ] 棧深度溢出
- - [√] ***代內(nèi)存溢出
- - [ ] 本地方法棧溢出
- - [ ] JVM棧內(nèi)存溢出
- - [ ] 堆溢出
- public static void testPergemOutOfMemory1(){
- //方法一失敗
- List<String> list = new ArrayList<String>();
- while(true){
- list.add(UUID.randomUUID().toString().intern());
- }
- }
打算把String常量池堆滿,沒想到失敗了,JDK1.7后常量池放到了堆里,也能進(jìn)行垃圾回收了傲。
馬上第二次嘗試,使用cglib,用Class把老年代取堆滿,嗯,說走咱就走啊。
- public static void testPergemOutOfMemory2(){
- try {
- while (true) {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(OOM.class);
- enhancer.setUseCache(false);
- enhancer.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- return proxy.invokeSuper(obj, args);
- }
- });
- enhancer.create();
- }
- }
- catch (Exception e){
- e.printStackTrace();
- }
- }
虛擬機(jī)成功gg了,那JDK動態(tài)代理產(chǎn)生的類能不能撐爆呢?
- public static void testPergemOutOfMemory3(){
- while(true){
- final OOM oom = new OOM();
- Proxy.newProxyInstance(oom.getClass().getClassLoader(), oom.getClass().getInterfaces(), new InvocationHandler() {
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Object result = method.invoke(oom, args);
- return result;
- }
- });
- }
- }
答案是不行!會進(jìn)行回收。JDK動態(tài)代理產(chǎn)生的類信息,不會放到***代中,而是放在堆中。
- - [ ] 棧深度溢出
- - [ ] ***代內(nèi)存溢出
- - [√] 本地方法棧溢出
- - [ ] JVM棧內(nèi)存溢出
- - [ ] 堆溢出
- public static void testNativeMethodOutOfMemory(){
- int j = 0;
- while(true){
- Printer.println(j++);
- ExecutorService executors = Executors.newFixedThreadPool(50);
- int i=0;
- while(i++<10){
- executors.submit(new Runnable() {
- public void run() {
- }
- });
- }
- }
- }
這個的原理就是不斷創(chuàng)建線程池,而每個線程池都創(chuàng)建10個線程,這些線程池都是在本地方法區(qū)的,久而久之,本地方法區(qū)就爆炸了。
- - [ ] 棧深度溢出
- - [ ] ***代內(nèi)存溢出
- - [ ] 本地方法棧溢出
- - [√] JVM棧內(nèi)存溢出
- - [ ] 堆溢出
- public static void testStackOutOfMemory(){
- while (true) {
- Thread thread = new Thread(new Runnable() {
- public void run() {
- while(true){
- }
- }
- });
- thread.start();
- }
- }
線程的創(chuàng)建會直接在JVM棧中創(chuàng)建,但是本例子中,沒看到爆炸,主機(jī)先掛了,不是JVM掛了,真的是主機(jī)掛了,無論在mac還是在windows,都掛了。溫馨提示,這個真的會死機(jī)的。。
- - [ ] 棧深度溢出
- - [ ] ***代內(nèi)存溢出
- - [ ] 本地方法棧溢出
- - [ ] JVM棧內(nèi)存溢出
- - [√] 堆溢出
- public static void testOutOfHeapMemory(){
- List<StringBuffer> list = new ArrayList<StringBuffer>();
- while(true){
- StringBuffer B = new StringBuffer();
- for(int i = 0 ; i < 10000 ; i++){
- B.append(i);
- }
- list.add(B);
- }
- }
好了終于到了最簡單的環(huán)節(jié),不斷往堆中塞新增的StringBuffer對象,堆滿了就直接爆炸了。
妥妥的。小伙伴們拿回去好好玩吧,就醬。
【本文為51CTO專欄作者“大蕉”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號“一名叫大蕉的程序員”獲取授權(quán)】